summaryrefslogtreecommitdiff
path: root/internal/generate/codegen_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-21 11:51:01 +0200
committerPaul Buetow <paul@buetow.org>2026-02-21 11:51:01 +0200
commit6c912a9d72ae2a43923c638538d320e6bf585952 (patch)
tree727f66d158210e01abf8c18a83ef4db6066e0c1a /internal/generate/codegen_test.go
parent32136b8cb18944157ff1f361bc0755f6b627fd47 (diff)
Migrate make targets to mage
Amp-Thread-ID: https://ampcode.com/threads/T-019c7f4e-cc5f-76f1-aaf0-dd7cbaabbb18 Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'internal/generate/codegen_test.go')
-rw-r--r--internal/generate/codegen_test.go263
1 files changed, 263 insertions, 0 deletions
diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go
new file mode 100644
index 0000000..b19a824
--- /dev/null
+++ b/internal/generate/codegen_test.go
@@ -0,0 +1,263 @@
+package generate
+
+import (
+ "strings"
+ "testing"
+)
+
+func generateFromPair(t *testing.T, enter, exit string) string {
+ t.Helper()
+ input := enter + "\n" + exit
+ formats := mustParseAll(t, input)
+ return GenerateTracepointsC(formats)
+}
+
+func TestGenerateFdHandler(t *testing.T) {
+ output := generateFromPair(t, FormatRead, FormatExitRead)
+
+ requireContains(t, output, `SEC("tracepoint/syscalls/sys_enter_read")`)
+ requireContains(t, output, "struct trace_event_raw_sys_enter *ctx")
+ requireContains(t, output, "struct fd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct fd_event), 0);")
+ requireContains(t, output, "ev->event_type = ENTER_FD_EVENT;")
+ requireContains(t, output, "ev->trace_id = SYS_ENTER_READ;")
+ requireContains(t, output, "ev->fd = (__s32)ctx->args[0];")
+ requireContains(t, output, "#define SYS_ENTER_READ 844")
+}
+
+func TestGenerateOpenHandler(t *testing.T) {
+ output := generateFromPair(t, FormatOpenat, FormatExitOpenat)
+
+ requireContains(t, output, `SEC("tracepoint/syscalls/sys_enter_openat")`)
+ requireContains(t, output, "struct open_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_OPEN_EVENT;")
+ requireContains(t, output, "ev->trace_id = SYS_ENTER_OPENAT;")
+ requireContains(t, output, "__builtin_memset(&(ev->filename), 0, sizeof(ev->filename) + sizeof(ev->comm));")
+ requireContains(t, output, "bpf_probe_read_user_str(ev->filename, sizeof(ev->filename), (void *)ctx->args[1]);")
+ requireContains(t, output, "bpf_get_current_comm(&ev->comm, sizeof(ev->comm));")
+ requireContains(t, output, "ev->flags = ctx->args[2];")
+}
+
+func TestGenerateOpenHandlerDirect(t *testing.T) {
+ output := generateFromPair(t, FormatOpen, FormatExitOpen)
+
+ requireContains(t, output, "bpf_probe_read_user_str(ev->filename, sizeof(ev->filename), (void *)ctx->args[0]);")
+ requireContains(t, output, "ev->flags = ctx->args[1];")
+}
+
+func TestGenerateOpenat2Handler(t *testing.T) {
+ f := mustParseOne(t, FormatOpenat2)
+ r := ClassifyFormat(&f)
+ if r.Kind != KindOpen {
+ t.Fatalf("openat2 classified as %d, want KindOpen", r.Kind)
+ }
+ // openat2 has filename at args[1] but flags field name = "how" (not "flags"),
+ // so FieldNumber("flags") returns -1
+ if n := f.FieldNumber("flags"); n != -1 {
+ t.Errorf("openat2 FieldNumber(flags) = %d, want -1", n)
+ }
+}
+
+func TestGenerateRetHandlerRead(t *testing.T) {
+ output := generateFromPair(t, FormatRead, FormatExitRead)
+
+ requireContains(t, output, `SEC("tracepoint/syscalls/sys_exit_read")`)
+ requireContains(t, output, "struct trace_event_raw_sys_exit *ctx")
+ requireContains(t, output, "struct ret_event *ev")
+ requireContains(t, output, "ev->event_type = EXIT_RET_EVENT;")
+ requireContains(t, output, "ev->trace_id = SYS_EXIT_READ;")
+ requireContains(t, output, "ev->ret = ctx->ret;")
+ requireContains(t, output, "ev->ret_type = READ_CLASSIFIED;")
+}
+
+func TestGenerateRetHandlerWrite(t *testing.T) {
+ output := generateFromPair(t, FormatWrite, FormatExitWrite)
+
+ requireContains(t, output, "ev->ret_type = WRITE_CLASSIFIED;")
+ requireContains(t, output, "ev->trace_id = SYS_EXIT_WRITE;")
+}
+
+func TestGenerateRetHandlerOpenat(t *testing.T) {
+ output := generateFromPair(t, FormatOpenat, FormatExitOpenat)
+
+ requireContains(t, output, "ev->ret_type = UNCLASSIFIED;")
+ requireContains(t, output, "ev->trace_id = SYS_EXIT_OPENAT;")
+}
+
+func TestGenerateNameHandler(t *testing.T) {
+ output := generateFromPair(t, FormatRename, FormatExitRename)
+
+ requireContains(t, output, `SEC("tracepoint/syscalls/sys_enter_rename")`)
+ requireContains(t, output, "struct name_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_NAME_EVENT;")
+ requireContains(t, output, "ev->trace_id = SYS_ENTER_RENAME;")
+ requireContains(t, output, "__builtin_memset(&(ev->oldname), 0, sizeof(ev->oldname) + sizeof(ev->newname));")
+ requireContains(t, output, "bpf_probe_read_user_str(ev->oldname, sizeof(ev->oldname), (void*)ctx->args[0]);")
+ requireContains(t, output, "bpf_probe_read_user_str(ev->newname, sizeof(ev->newname), (void*)ctx->args[1]);")
+}
+
+func TestGeneratePathnameHandler(t *testing.T) {
+ // Use exit_unlink (same structure as exit_read) paired with enter_unlink
+ exitUnlink := strings.Replace(FormatExitRead, "sys_exit_read", "sys_exit_unlink", 1)
+ exitUnlink = strings.Replace(exitUnlink, "ID: 843", "ID: 883", 1)
+ output := generateFromPair(t, FormatUnlink, exitUnlink)
+
+ requireContains(t, output, "struct path_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_PATH_EVENT;")
+ requireContains(t, output, "__builtin_memset(&(ev->pathname), 0, sizeof(ev->pathname));")
+ requireContains(t, output, "bpf_probe_read_user_str(ev->pathname, sizeof(ev->pathname), (void*)ctx->args[0]);")
+}
+
+func TestGenerateFcntlHandler(t *testing.T) {
+ output := generateFromPair(t, FormatFcntl, FormatExitFcntl)
+
+ requireContains(t, output, "struct fcntl_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_FCNTL_EVENT;")
+ requireContains(t, output, "ev->fd = ctx->args[0];")
+ requireContains(t, output, "ev->cmd = ctx->args[1];")
+ requireContains(t, output, "ev->arg = ctx->args[2];")
+}
+
+func TestGenerateNullHandler(t *testing.T) {
+ output := generateFromPair(t, FormatSync, FormatExitSync)
+
+ requireContains(t, output, "struct null_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_NULL_EVENT;")
+ requireContains(t, output, "ev->trace_id = SYS_ENTER_SYNC;")
+ // Null handler should NOT have ev->fd, ev->filename, etc.
+ if strings.Contains(output, "ev->fd") {
+ t.Error("null handler should not have ev->fd")
+ }
+}
+
+func TestGenerateDup3Handler(t *testing.T) {
+ output := generateFromPair(t, FormatDup3, FormatExitDup3)
+
+ requireContains(t, output, "struct dup3_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_DUP3_EVENT;")
+ requireContains(t, output, "ev->fd = (__s32)ctx->args[0];")
+ requireContains(t, output, "ev->flags = (__s32)ctx->args[2];")
+}
+
+func TestGenerateOpenByHandleAtHandler(t *testing.T) {
+ output := generateFromPair(t, FormatOpenByHandleAt, FormatExitOpenByHandleAt)
+
+ requireContains(t, output, "struct open_by_handle_at_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_OPEN_BY_HANDLE_AT_EVENT;")
+ requireContains(t, output, "ev->flags = (__s32)ctx->args[2];")
+}
+
+func TestGenerateIgnoredComment(t *testing.T) {
+ output := generateFromPair(t, FormatKill, FormatExitKill)
+
+ requireContains(t, output, "/// Ignoring sys_enter_kill sys_exit_kill as possibly not file I/O related")
+}
+
+func TestGenerateDefineConstants(t *testing.T) {
+ output := generateFromPair(t, FormatRead, FormatExitRead)
+
+ requireContains(t, output, "#define SYS_ENTER_READ 844")
+ requireContains(t, output, "#define SYS_EXIT_READ 843")
+}
+
+func TestGenerateDefinesSortedByIDDesc(t *testing.T) {
+ input := FormatRead + "\n" + FormatExitRead + "\n" + FormatClose + "\n" + FormatExitClose
+ formats := mustParseAll(t, input)
+ output := GenerateTracepointsC(formats)
+
+ enterReadPos := strings.Index(output, "#define SYS_ENTER_READ")
+ enterClosePos := strings.Index(output, "#define SYS_ENTER_CLOSE")
+ if enterReadPos < 0 || enterClosePos < 0 {
+ t.Fatal("missing #define lines")
+ }
+ if enterReadPos > enterClosePos {
+ t.Error("#define SYS_ENTER_READ (844) should come before SYS_ENTER_CLOSE (778)")
+ }
+}
+
+func TestGenerateHandlerStructure(t *testing.T) {
+ output := generateFromPair(t, FormatClose, FormatExitClose)
+
+ requireContains(t, output, "int handle_sys_enter_close(struct trace_event_raw_sys_enter *ctx) {")
+ requireContains(t, output, "__u32 pid, tid;")
+ requireContains(t, output, "if (filter(&pid, &tid))")
+ requireContains(t, output, "ev->pid = pid;")
+ requireContains(t, output, "ev->tid = tid;")
+ requireContains(t, output, "ev->time = bpf_ktime_get_boot_ns();")
+ requireContains(t, output, "bpf_ringbuf_submit(ev, 0);")
+ requireContains(t, output, "return 0;")
+}
+
+func TestGenerateAllEventTypes(t *testing.T) {
+ // Verify every event type constant appears correctly
+ tests := []struct {
+ kind TracepointKind
+ enter string
+ exit string
+ }{
+ {KindFd, "ENTER_FD_EVENT", "EXIT_FD_EVENT"},
+ {KindOpen, "ENTER_OPEN_EVENT", "EXIT_OPEN_EVENT"},
+ {KindPathname, "ENTER_PATH_EVENT", "EXIT_PATH_EVENT"},
+ {KindName, "ENTER_NAME_EVENT", "EXIT_NAME_EVENT"},
+ {KindRet, "ENTER_RET_EVENT", "EXIT_RET_EVENT"},
+ {KindFcntl, "ENTER_FCNTL_EVENT", "EXIT_FCNTL_EVENT"},
+ {KindNull, "ENTER_NULL_EVENT", "EXIT_NULL_EVENT"},
+ {KindDup3, "ENTER_DUP3_EVENT", "EXIT_DUP3_EVENT"},
+ {KindOpenByHandleAt, "ENTER_OPEN_BY_HANDLE_AT_EVENT", "EXIT_OPEN_BY_HANDLE_AT_EVENT"},
+ }
+
+ for _, tt := range tests {
+ if got := eventTypeConstant(tt.kind, true); got != tt.enter {
+ t.Errorf("eventTypeConstant(%d, true) = %q, want %q", tt.kind, got, tt.enter)
+ }
+ if got := eventTypeConstant(tt.kind, false); got != tt.exit {
+ t.Errorf("eventTypeConstant(%d, false) = %q, want %q", tt.kind, got, tt.exit)
+ }
+ }
+}
+
+func TestEventStructNames(t *testing.T) {
+ tests := []struct {
+ kind TracepointKind
+ want string
+ }{
+ {KindFd, "fd_event"},
+ {KindOpen, "open_event"},
+ {KindPathname, "path_event"},
+ {KindName, "name_event"},
+ {KindRet, "ret_event"},
+ {KindFcntl, "fcntl_event"},
+ {KindNull, "null_event"},
+ {KindDup3, "dup3_event"},
+ {KindOpenByHandleAt, "open_by_handle_at_event"},
+ }
+
+ for _, tt := range tests {
+ if got := eventStructName(tt.kind); got != tt.want {
+ t.Errorf("eventStructName(%d) = %q, want %q", tt.kind, got, tt.want)
+ }
+ }
+}
+
+func TestEnterReject(t *testing.T) {
+ // RetTracepoint as enter type should be rejected
+ if !isEnterRejected(KindRet) {
+ t.Error("KindRet should be enter-rejected")
+ }
+ if !isEnterRejected(KindNone) {
+ t.Error("KindNone should be enter-rejected")
+ }
+
+ accepted := []TracepointKind{KindFd, KindOpen, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt}
+ for _, k := range accepted {
+ if isEnterRejected(k) {
+ t.Errorf("kind %d should NOT be enter-rejected", k)
+ }
+ }
+}
+
+func requireContains(t *testing.T, haystack, needle string) {
+ t.Helper()
+ if !strings.Contains(haystack, needle) {
+ t.Errorf("output missing expected string: %q", needle)
+ }
+}