summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-21 21:56:36 +0200
committerPaul Buetow <paul@buetow.org>2026-02-21 21:56:36 +0200
commit311b827599251d8d68526293815e8d4dcba632c8 (patch)
tree589d01f5653f966c3b2cc786911b8cc02a3237c9
parent36f216c757eea7db82cf04aeae592956199b9f76 (diff)
Add negative tests for all internal unit tests (task 348)
- internal/types: Fix StringValue panic with no null terminator, add negative tests for serialization, Equals, and StringValue edge cases - internal/file: Add negative tests for empty name, unknown flags, SetFlags/AddFlags, Dup, empty OldnameNewname and Pathname - internal/flamegraph: Add negative tests for StringByName unknown field, Counter.ValueByName panic, merge empty, deserialize invalid data, serialize/deserialize round-trip - internal/generate/format: Add negative tests for empty input, ID errors, malformed fields, empty declarations - internal/generate/typesgo: Add negative tests for snakeToCamel edge cases, unknown types, invalid member/define parsing, no-import case - internal/generate/tracepointsgo: Add negative tests for malformed SEC, no SEC lines - internal/generate/codegen: Add negative tests for unknown event kind, invalid syscall grouping, missing exit tracepoint - internal/generate/classify: Add negative tests for empty external fields, non-fd types Amp-Thread-ID: https://ampcode.com/threads/T-019c81bf-3d5c-7216-b1b6-890db1374414 Co-authored-by: Amp <amp@ampcode.com>
-rw-r--r--internal/file/file_test.go86
-rw-r--r--internal/flamegraph/iordata_test.go156
-rw-r--r--internal/generate/classify_test.go29
-rw-r--r--internal/generate/codegen_test.go35
-rw-r--r--internal/generate/format_test.go40
-rw-r--r--internal/generate/tracepointsgo_test.go29
-rw-r--r--internal/generate/typesgo_test.go69
-rw-r--r--internal/types/string.go6
-rw-r--r--internal/types/types_test.go93
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)