diff options
| -rw-r--r-- | internal/file/file_test.go | 86 | ||||
| -rw-r--r-- | internal/flamegraph/iordata_test.go | 156 | ||||
| -rw-r--r-- | internal/generate/classify_test.go | 29 | ||||
| -rw-r--r-- | internal/generate/codegen_test.go | 35 | ||||
| -rw-r--r-- | internal/generate/format_test.go | 40 | ||||
| -rw-r--r-- | internal/generate/tracepointsgo_test.go | 29 | ||||
| -rw-r--r-- | internal/generate/typesgo_test.go | 69 | ||||
| -rw-r--r-- | internal/types/string.go | 6 | ||||
| -rw-r--r-- | internal/types/types_test.go | 93 |
9 files changed, 542 insertions, 1 deletions
diff --git a/internal/file/file_test.go b/internal/file/file_test.go index 726de5a..684a7d8 100644 --- a/internal/file/file_test.go +++ b/internal/file/file_test.go @@ -2,6 +2,8 @@ package file import ( "ior/internal/types" + "strings" + "syscall" "testing" ) @@ -20,3 +22,87 @@ func TestNewFdUnknownFlags(t *testing.T) { t.Errorf("expected unknown flags, got %v", fdFile.Flags()) } } + +func TestNewFdEmptyName(t *testing.T) { + fdFile := NewFd(1, "", 0) + str := fdFile.String() + if !strings.Contains(str, "E:name") { + t.Errorf("expected String() to contain 'E:name' for empty name, got '%s'", str) + } +} + +func TestFlagsIsUnknown(t *testing.T) { + f := unknownFlag + if f.Is(syscall.O_RDONLY) { + t.Errorf("expected Is(O_RDONLY) to be false for unknownFlag") + } + if f.Is(syscall.O_WRONLY) { + t.Errorf("expected Is(O_WRONLY) to be false for unknownFlag") + } + if f.Is(syscall.O_RDWR) { + t.Errorf("expected Is(O_RDWR) to be false for unknownFlag") + } +} + +func TestFlagsStringUnknown(t *testing.T) { + f := Flags(-1) + if f.String() != "O_NONE" { + t.Errorf("expected 'O_NONE' for unknown flags, got '%s'", f.String()) + } +} + +func TestNewOldnameNewnameEmpty(t *testing.T) { + var oldname, newname [128]byte + f := NewOldnameNewname(oldname[:], newname[:]) + if f.Name() != "" { + t.Errorf("expected empty Name(), got '%s'", f.Name()) + } + if !strings.Contains(f.String(), "old:") || !strings.Contains(f.String(), "->new:") { + t.Errorf("expected String() to contain 'old:' and '->new:', got '%s'", f.String()) + } +} + +func TestNewPathnameEmpty(t *testing.T) { + var pathname [128]byte + f := NewPathname(pathname[:]) + if f.Name() != "" { + t.Errorf("expected empty Name(), got '%s'", f.Name()) + } + if !strings.Contains(f.String(), "pathname:") { + t.Errorf("expected String() to contain 'pathname:', got '%s'", f.String()) + } +} + +func TestFdFileSetFlags(t *testing.T) { + fdFile := NewFd(1, "test.txt", 0) + if fdFile.Flags() != Flags(0) { + t.Errorf("expected flags 0, got %v", fdFile.Flags()) + } + fdFile.SetFlags(syscall.O_WRONLY) + if fdFile.Flags() != Flags(syscall.O_WRONLY) { + t.Errorf("expected O_WRONLY after SetFlags, got %v", fdFile.Flags()) + } +} + +func TestFdFileAddFlags(t *testing.T) { + fdFile := NewFd(1, "test.txt", syscall.O_RDWR) + fdFile.AddFlags(syscall.O_APPEND) + expected := Flags(syscall.O_RDWR | syscall.O_APPEND) + if fdFile.Flags() != expected { + t.Errorf("expected O_RDWR|O_APPEND after AddFlags, got %v", fdFile.Flags()) + } +} + +func TestFdFileDup(t *testing.T) { + fdFile := NewFd(1, "original.txt", syscall.O_RDONLY) + duped := fdFile.Dup(42) + if duped.Name() != "original.txt" { + t.Errorf("expected duped name 'original.txt', got '%s'", duped.Name()) + } + if !strings.Contains(duped.String(), "42") { + t.Errorf("expected duped String() to contain fd 42, got '%s'", duped.String()) + } + if strings.Contains(duped.String(), "%(1,") { + t.Errorf("expected duped String() to NOT contain old fd 1, got '%s'", duped.String()) + } +} diff --git a/internal/flamegraph/iordata_test.go b/internal/flamegraph/iordata_test.go index 9957f9e..f499e5f 100644 --- a/internal/flamegraph/iordata_test.go +++ b/internal/flamegraph/iordata_test.go @@ -1,6 +1,7 @@ package flamegraph import ( + "bytes" "ior/internal/types" "syscall" "testing" @@ -109,6 +110,161 @@ func TestMerge(t *testing.T) { // }) } +func TestStringByNameUnknownField(t *testing.T) { + ir := IterRecord{ + Path: "/tmp/test", + TraceID: types.SYS_ENTER_OPENAT, + Comm: "testComm", + Pid: 1234, + Tid: 5678, + Flags: flagsType(syscall.O_RDONLY), + Cnt: Counter{Count: 1}, + } + + _, err := ir.StringByName("nonexistent") + if err == nil { + t.Error("Expected error for unknown field name, got nil") + } +} + +func TestStringByNameValidFields(t *testing.T) { + ir := IterRecord{ + Path: "/tmp/test", + TraceID: types.SYS_ENTER_OPENAT, + Comm: "testComm", + Pid: 1234, + Tid: 5678, + Flags: flagsType(syscall.O_RDONLY), + Cnt: Counter{Count: 1}, + } + + validFields := []string{"path", "comm", "tracepoint", "pid", "tid", "flags"} + for _, name := range validFields { + t.Run(name, func(t *testing.T) { + val, err := ir.StringByName(name) + if err != nil { + t.Errorf("Expected no error for field %q, got %v", name, err) + } + if val == "" { + t.Errorf("Expected non-empty string for field %q", name) + } + }) + } +} + +func TestCounterValueByNamePanic(t *testing.T) { + c := Counter{Count: 1, Duration: 100, DurationToPrev: 10, Bytes: 64} + + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for unknown counter name, got none") + } + }() + + c.ValueByName("nonexistent") +} + +func TestMergeEmpty(t *testing.T) { + traceId := types.SYS_ENTER_OPENAT + roFlag := flagsType(syscall.O_RDONLY) + + iod := iorData{paths: pathMap{ + "path1": {traceId: {"comm1": {100: {1000: {roFlag: Counter{ + Count: 10, + Duration: 1000, + DurationToPrev: 100, + Bytes: 64, + }}}}}}, + }} + + empty := newIorData() + merged := iod.merge(empty) + + if len(merged.paths) != 1 { + t.Errorf("Expected 1 path, got %d", len(merged.paths)) + } + cnt := merged.paths["path1"][traceId]["comm1"][100][1000][roFlag] + if cnt.Count != 10 || cnt.Duration != 1000 || cnt.DurationToPrev != 100 || cnt.Bytes != 64 { + t.Errorf("Expected original counter preserved, got %v", cnt) + } +} + +func TestAddZeroCounter(t *testing.T) { + iod := newIorData() + path := pathType("testPath") + traceId := types.SYS_ENTER_OPENAT + comm := commType("testComm") + pid := pidType(1234) + tid := tidType(5678) + flags := flagsType(syscall.O_RDONLY) + zero := Counter{} + + iod.add(path, traceId, comm, pid, tid, flags, zero) + + cnt, ok := iod.paths[path][traceId][comm][pid][tid][flags] + if !ok { + t.Fatal("Expected entry to exist for zero counter") + } + if cnt != zero { + t.Errorf("Expected zero counter %v, got %v", zero, cnt) + } +} + +func TestSerializeDeserializeRoundTrip(t *testing.T) { + traceId := types.SYS_ENTER_OPENAT + rdwrFlag := flagsType(syscall.O_RDWR) + roFlag := flagsType(syscall.O_RDONLY) + + original := iorData{paths: pathMap{ + "path1": {traceId: {"comm1": {100: {1000: {rdwrFlag: Counter{ + Count: 10, + Duration: 1000, + DurationToPrev: 100, + Bytes: 64, + }}}}}}, + "path2": {traceId: {"comm2": {200: {2000: {roFlag: Counter{ + Count: 20, + Duration: 2000, + DurationToPrev: 200, + Bytes: 128, + }}}}}}, + }} + + data, err := original.serialize() + if err != nil { + t.Fatalf("serialize failed: %v", err) + } + + restored := newIorData() + if err := restored.deserialize(bytes.NewBuffer(data)); err != nil { + t.Fatalf("deserialize failed: %v", err) + } + + if len(restored.paths) != len(original.paths) { + t.Fatalf("Expected %d paths, got %d", len(original.paths), len(restored.paths)) + } + + cnt1 := restored.paths["path1"][traceId]["comm1"][100][1000][rdwrFlag] + if cnt1.Count != 10 || cnt1.Duration != 1000 || cnt1.DurationToPrev != 100 || cnt1.Bytes != 64 { + t.Errorf("path1 counter mismatch: %v", cnt1) + } + + cnt2 := restored.paths["path2"][traceId]["comm2"][200][2000][roFlag] + if cnt2.Count != 20 || cnt2.Duration != 2000 || cnt2.DurationToPrev != 200 || cnt2.Bytes != 128 { + t.Errorf("path2 counter mismatch: %v", cnt2) + } +} + +func TestDeserializeInvalidData(t *testing.T) { + iod := newIorData() + var buf bytes.Buffer + buf.WriteString("this is not valid gob data") + err := iod.deserialize(&buf) + if err == nil { + t.Error("Expected error when deserializing invalid data, got nil") + } +} + func bothArraysHaveSameElements(a, b []string) bool { if len(a) != len(b) { return false diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go index 950e056..07c6027 100644 --- a/internal/generate/classify_test.go +++ b/internal/generate/classify_test.go @@ -341,6 +341,35 @@ func TestClassifySyscallPairIgnored(t *testing.T) { } } +func TestClassifyFormatNoExternalFields(t *testing.T) { + f := &Format{ + Name: "sys_enter_test", + ID: 999, + ExternalFields: nil, + } + r := ClassifyFormat(f) + if r.Kind != KindNone { + t.Errorf("ClassifyFormat with empty ExternalFields: got kind %d, want KindNone", r.Kind) + } +} + +func TestIsFdTypeNonMatch(t *testing.T) { + nonFdTypes := []string{ + "const char *", + "long", + "size_t", + "pid_t", + "umode_t", + "char *", + "void *", + } + for _, typ := range nonFdTypes { + if isFdType(typ) { + t.Errorf("isFdType(%q) = true, want false", typ) + } + } +} + func mustParseAll(t *testing.T, data string) []Format { t.Helper() formats, err := ParseFormats(strings.NewReader(data)) diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go index 23ab7c1..6402214 100644 --- a/internal/generate/codegen_test.go +++ b/internal/generate/codegen_test.go @@ -285,6 +285,41 @@ func TestEnterReject(t *testing.T) { } } +func TestEventStructNameUnknown(t *testing.T) { + if got := eventStructName(TracepointKind(999)); got != "unknown_event" { + t.Errorf("eventStructName(999) = %q, want \"unknown_event\"", got) + } +} + +func TestEventTypeConstantUnknown(t *testing.T) { + if got := eventTypeConstant(TracepointKind(999), true); got != "ENTER_UNKNOWN_EVENT" { + t.Errorf("eventTypeConstant(999, true) = %q, want \"ENTER_UNKNOWN_EVENT\"", got) + } + if got := eventTypeConstant(TracepointKind(999), false); got != "EXIT_UNKNOWN_EVENT" { + t.Errorf("eventTypeConstant(999, false) = %q, want \"EXIT_UNKNOWN_EVENT\"", got) + } +} + +func TestGroupBySyscallInvalid(t *testing.T) { + formats := []Format{ + {Name: "tooshort", ID: 1}, + {Name: "also_short", ID: 2}, + } + output := GenerateTracepointsC(formats) + if strings.Contains(output, "SEC(") { + t.Error("formats with fewer than 3 name parts should not produce SEC handlers") + } +} + +func TestClassifySyscallNoExit(t *testing.T) { + formats := mustParseAll(t, FormatRead) + output := GenerateTracepointsC(formats) + requireContains(t, output, "Ignoring") + if strings.Contains(output, "SEC(") { + t.Error("syscall with only enter and no exit should be ignored") + } +} + func requireContains(t *testing.T, haystack, needle string) { t.Helper() if !strings.Contains(haystack, needle) { diff --git a/internal/generate/format_test.go b/internal/generate/format_test.go index f8f078b..a77ee2a 100644 --- a/internal/generate/format_test.go +++ b/internal/generate/format_test.go @@ -172,3 +172,43 @@ func TestParseFormatError(t *testing.T) { t.Error("expected error for field without name") } } + +func TestParseFormatEmptyInput(t *testing.T) { + formats, err := ParseFormats(strings.NewReader("")) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(formats) != 0 { + t.Errorf("expected 0 formats, got %d", len(formats)) + } +} + +func TestParseFormatIDWithoutName(t *testing.T) { + _, err := ParseFormats(strings.NewReader("ID: 123\n")) + if err == nil { + t.Error("expected error for ID without preceding name") + } +} + +func TestParseFormatInvalidID(t *testing.T) { + input := "name: sys_enter_test\nID: notanumber\n" + _, err := ParseFormats(strings.NewReader(input)) + if err == nil { + t.Error("expected error for non-numeric ID") + } +} + +func TestParseFieldNotEnoughParts(t *testing.T) { + input := "name: sys_enter_test\nID: 100\nfield:unsigned int fd; offset:16\n" + _, err := ParseFormats(strings.NewReader(input)) + if err == nil { + t.Error("expected error for field with fewer than 4 semicolons") + } +} + +func TestSplitDeclarationEmpty(t *testing.T) { + typePart, namePart := splitDeclaration("") + if typePart != "" || namePart != "" { + t.Errorf("splitDeclaration(\"\") = (%q, %q), want (\"\", \"\")", typePart, namePart) + } +} diff --git a/internal/generate/tracepointsgo_test.go b/internal/generate/tracepointsgo_test.go index d0f90db..cd24942 100644 --- a/internal/generate/tracepointsgo_test.go +++ b/internal/generate/tracepointsgo_test.go @@ -90,3 +90,32 @@ func TestExtractTracepointsPackageHeader(t *testing.T) { t.Errorf("unexpected header: %s", output[:60]) } } + +func TestExtractTracepointsMalformedSEC(t *testing.T) { + input := `SEC("tracepoint/not_matching_pattern") +int handle_something(struct trace_event_raw_sys_enter *ctx) { + return 0; +} +SEC("some/garbage") +` + output, err := ExtractTracepoints(strings.NewReader(input)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if strings.Contains(output, `"sys_`) { + t.Error("malformed SEC lines should not produce tracepoints") + } + requireContains(t, output, "var List = []string{") +} + +func TestExtractTracepointsNoSECLines(t *testing.T) { + input := "// just a comment\n/* another comment */\nint foo() { return 0; }\n" + output, err := ExtractTracepoints(strings.NewReader(input)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + requireContains(t, output, "var List = []string{") + if strings.Contains(output, `"sys_`) { + t.Error("input with no SEC lines should produce empty list") + } +} diff --git a/internal/generate/typesgo_test.go b/internal/generate/typesgo_test.go index f1085b5..89dafa8 100644 --- a/internal/generate/typesgo_test.go +++ b/internal/generate/typesgo_test.go @@ -255,3 +255,72 @@ func TestGenerateTypesGoPackageDecl(t *testing.T) { t.Errorf("unexpected package header: %s", output[:80]) } } + +func TestSnakeToCamelEmpty(t *testing.T) { + if got := snakeToCamel(""); got != "" { + t.Errorf("snakeToCamel(\"\") = %q, want \"\"", got) + } +} + +func TestSnakeToCamelLeadingUnderscore(t *testing.T) { + got := snakeToCamel("__u32") + if got != "U32" { + t.Errorf("snakeToCamel(\"__u32\") = %q, want \"U32\"", got) + } +} + +func TestCTypeToGoTypeUnknown(t *testing.T) { + if got := cTypeToGoType("some_custom_type"); got != "some_custom_type" { + t.Errorf("cTypeToGoType(\"some_custom_type\") = %q, want \"some_custom_type\"", got) + } +} + +func TestParseMemberInvalid(t *testing.T) { + tests := []string{ + "not a valid member line", + "too many words here that do not match", + "", + ";;;", + } + for _, input := range tests { + _, ok := parseMember(input) + if ok { + t.Errorf("parseMember(%q) returned ok=true, want false", input) + } + } +} + +func TestParseDefineInvalid(t *testing.T) { + tests := []string{ + "#define ONLY_NAME", + "#define", + "", + } + for _, input := range tests { + _, ok := parseDefine(input) + if ok { + t.Errorf("parseDefine(%q) returned ok=true, want false", input) + } + } +} + +func TestAddTypesImportsNoImport(t *testing.T) { + code := "package types\n\nconst FOO = 1\n" + got := AddTypesImports(code) + if got != code { + t.Errorf("AddTypesImports should not modify code without fmt/sync/binary usage, got:\n%s", got) + } +} + +func TestParseCTypesInputEmpty(t *testing.T) { + structs, constants, err := ParseCTypesInput(strings.NewReader("")) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(structs) != 0 { + t.Errorf("expected 0 structs, got %d", len(structs)) + } + if len(constants) != 0 { + t.Errorf("expected 0 constants, got %d", len(constants)) + } +} diff --git a/internal/types/string.go b/internal/types/string.go index 363b746..5cfed34 100644 --- a/internal/types/string.go +++ b/internal/types/string.go @@ -4,5 +4,9 @@ import "bytes" // As data comes in from arrays, converted to slices, there will be null-bytes at the end.. func StringValue(byteStr []byte) string { - return string(byteStr[:bytes.IndexByte(byteStr, 0)]) + idx := bytes.IndexByte(byteStr, 0) + if idx == -1 { + return string(byteStr) + } + return string(byteStr[:idx]) } diff --git a/internal/types/types_test.go b/internal/types/types_test.go index 0968def..6abebdb 100644 --- a/internal/types/types_test.go +++ b/internal/types/types_test.go @@ -63,6 +63,99 @@ func TestSerialization(t *testing.T) { } +func TestStringValueAllZeros(t *testing.T) { + data := make([]byte, MAX_FILENAME_LENGTH) + result := StringValue(data) + assertEquals(t, "", result) + t.Log("StringValue with all-zero bytes returns empty string") +} + +func TestStringValueNoNullTerminator(t *testing.T) { + data := make([]byte, MAX_FILENAME_LENGTH) + for i := range data { + data[i] = 'A' + } + result := StringValue(data) + assertEquals(t, MAX_FILENAME_LENGTH, len(result)) + for i := range result { + assertEquals(t, byte('A'), result[i]) + } + t.Log("StringValue with no null terminator returns full content") +} + +func TestFdEventSerialization(t *testing.T) { + fdEv1 := FdEvent{ + EventType: ENTER_FD_EVENT, + TraceId: SYS_ENTER_READ, + Time: 987654321, + Pid: 20, + Tid: 21, + Fd: 3, + } + bytes, err := fdEv1.Bytes() + if err != nil { + t.Error(err) + } + fdEv2 := NewFdEvent(bytes) + + assertEquals(t, fdEv1.EventType, fdEv2.EventType) + assertEquals(t, fdEv1.TraceId, fdEv2.TraceId) + assertEquals(t, fdEv1.Time, fdEv2.Time) + assertEquals(t, fdEv1.Pid, fdEv2.Pid) + assertEquals(t, fdEv1.Tid, fdEv2.Tid) + assertEquals(t, fdEv1.Fd, fdEv2.Fd) + t.Log("FdEvent could be serialized correctly") +} + +func TestNullEventSerialization(t *testing.T) { + nullEv1 := NullEvent{ + EventType: ENTER_NULL_EVENT, + TraceId: SYS_ENTER_SYNC, + Time: 111222333, + Pid: 30, + Tid: 31, + } + bytes, err := nullEv1.Bytes() + if err != nil { + t.Error(err) + } + nullEv2 := NewNullEvent(bytes) + + assertEquals(t, nullEv1.EventType, nullEv2.EventType) + assertEquals(t, nullEv1.TraceId, nullEv2.TraceId) + assertEquals(t, nullEv1.Time, nullEv2.Time) + assertEquals(t, nullEv1.Pid, nullEv2.Pid) + assertEquals(t, nullEv1.Tid, nullEv2.Tid) + t.Log("NullEvent could be serialized correctly") +} + +func TestEqualsDifferentTypes(t *testing.T) { + openEv := OpenEvent{EventType: ENTER_OPEN_EVENT, TraceId: SYS_ENTER_OPENAT, Time: 1, Pid: 1, Tid: 1} + nullEv := NullEvent{EventType: ENTER_NULL_EVENT, TraceId: SYS_ENTER_SYNC, Time: 1, Pid: 1, Tid: 1} + fdEv := FdEvent{EventType: ENTER_FD_EVENT, TraceId: SYS_ENTER_READ, Time: 1, Pid: 1, Tid: 1, Fd: 1} + retEv := RetEvent{EventType: EXIT_OPEN_EVENT, TraceId: SYS_EXIT_OPENAT, Time: 1, Pid: 1, Tid: 1} + + assertEquals(t, false, openEv.Equals(&nullEv)) + assertEquals(t, false, openEv.Equals(&fdEv)) + assertEquals(t, false, nullEv.Equals(&openEv)) + assertEquals(t, false, fdEv.Equals(&retEv)) + assertEquals(t, false, retEv.Equals(&openEv)) + t.Log("Equals returns false for different event types") +} + +func TestEqualsDifferentValues(t *testing.T) { + fdEv1 := FdEvent{EventType: ENTER_FD_EVENT, TraceId: SYS_ENTER_READ, Time: 1, Pid: 1, Tid: 1, Fd: 3} + fdEv2 := FdEvent{EventType: ENTER_FD_EVENT, TraceId: SYS_ENTER_READ, Time: 1, Pid: 1, Tid: 1, Fd: 5} + + assertEquals(t, false, fdEv1.Equals(&fdEv2)) + + nullEv1 := NullEvent{EventType: ENTER_NULL_EVENT, TraceId: SYS_ENTER_SYNC, Time: 100, Pid: 1, Tid: 1} + nullEv2 := NullEvent{EventType: ENTER_NULL_EVENT, TraceId: SYS_ENTER_SYNC, Time: 200, Pid: 1, Tid: 1} + + assertEquals(t, false, nullEv1.Equals(&nullEv2)) + t.Log("Equals returns false for same type but different values") +} + func assertEquals[T comparable](t *testing.T, a, b T) { if a != b { t.Errorf("Expected %v, got %v", a, b) |
