summaryrefslogtreecommitdiff
path: root/integrationtests/expectations.go
blob: 36fdf6eaa956ab23e1a1d792f3255703c00fccb1 (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
112
113
114
115
116
package integrationtests

import (
	"strings"
	"testing"

	"ior/internal/flamegraph"
)

// ExpectedEvent describes an I/O event that should appear in the test output.
type ExpectedEvent struct {
	PathContains string // substring match on file path
	Tracepoint   string // tracepoint name substring, e.g. "openat"
	Comm         string // expected comm name, e.g. "ioworkload"
	MinCount     uint64 // minimum total occurrences across all matching records
}

// AssertEventsPresent verifies that each expected event is found in the test result.
// Counts are summed across all matching records before comparing to MinCount.
func AssertEventsPresent(t *testing.T, result TestResult, expected []ExpectedEvent) {
	t.Helper()
	for _, exp := range expected {
		var totalCount uint64
		var matched bool
		for _, rec := range result.Records {
			if matchesExpectation(rec, exp) {
				matched = true
				totalCount += rec.Cnt.Count
			}
		}
		if !matched {
			t.Errorf("expected event not found: %+v", exp)
			continue
		}
		if exp.MinCount > 0 && totalCount < exp.MinCount {
			t.Errorf("event matching %+v has total count %d, want >= %d",
				exp, totalCount, exp.MinCount)
		}
	}
}

// AssertNoUnexpectedComm verifies all records have the expected comm name.
// Records with empty comm are skipped because BPF may capture events before
// the process name is set in the task struct.
func AssertNoUnexpectedComm(t *testing.T, result TestResult, expectedComm string) {
	t.Helper()
	var count int
	for _, rec := range result.Records {
		if rec.Comm == "" {
			continue
		}
		if rec.Comm != expectedComm {
			count++
			if count <= 5 {
				t.Logf("unexpected comm %q (pid=%d tracepoint=%s path=%q)",
					rec.Comm, rec.Pid, rec.TraceID.String(), rec.Path)
			}
		}
	}
	if count > 0 {
		t.Fatalf("found %d records with unexpected comm (want %q)", count, expectedComm)
	}
}

// AssertNoUnexpectedPID verifies all records belong to the expected PID.
// Accepts int to match os.Getpid() return type.
func AssertNoUnexpectedPID(t *testing.T, result TestResult, expectedPID int) {
	t.Helper()
	pid := uint32(expectedPID)
	var count int
	for _, rec := range result.Records {
		if rec.Pid != pid {
			count++
			if count <= 5 {
				t.Logf("unexpected PID %d (tracepoint=%s path=%q comm=%q)",
					rec.Pid, rec.TraceID.String(), rec.Path, rec.Comm)
			}
		}
	}
	if count > 0 {
		t.Fatalf("found %d records with unexpected PID (want %d)", count, expectedPID)
	}
}

// AssertEventsAbsent verifies that none of the specified events appear in the test result.
// Each ExpectedEvent must have at least one filter field set to avoid accidentally
// matching all records.
func AssertEventsAbsent(t *testing.T, result TestResult, absent []ExpectedEvent) {
	t.Helper()
	for _, exp := range absent {
		if exp.PathContains == "" && exp.Tracepoint == "" && exp.Comm == "" {
			t.Errorf("AssertEventsAbsent: ExpectedEvent must have at least one filter field set: %+v", exp)
			continue
		}
		for _, rec := range result.Records {
			if matchesExpectation(rec, exp) {
				t.Errorf("event should be absent but was found: %+v (path=%q tracepoint=%s comm=%q)",
					exp, rec.Path, rec.TraceID.String(), rec.Comm)
				break
			}
		}
	}
}

func matchesExpectation(rec flamegraph.IterRecord, exp ExpectedEvent) bool {
	if exp.PathContains != "" && !strings.Contains(rec.Path, exp.PathContains) {
		return false
	}
	if exp.Tracepoint != "" && !strings.Contains(rec.TraceID.String(), exp.Tracepoint) {
		return false
	}
	if exp.Comm != "" && rec.Comm != exp.Comm {
		return false
	}
	return true
}