Skip to content

Commit fd637fc

Browse files
committed
test: add unit tests for tracee man command
Tests cover different event types: - Syscall events (openat) - Built-in eBPF events (sched_process_exec, security_file_open) - Signature events (gracefully handles when not available in test env) - Event classification and type checking - Error handling for non-existent events
1 parent fdad808 commit fd637fc

File tree

1 file changed

+378
-0
lines changed

1 file changed

+378
-0
lines changed

cmd/tracee/cmd/man_test.go

Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"os"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
12+
"github.com/aquasecurity/tracee/pkg/events"
13+
)
14+
15+
func TestShowEventDocumentation(t *testing.T) {
16+
// Use the shared signature loading from man.go
17+
ensureSignaturesLoaded()
18+
19+
testCases := []struct {
20+
name string
21+
eventName string
22+
expectedError error
23+
shouldFind bool
24+
eventType string
25+
}{
26+
{
27+
name: "syscall event - openat",
28+
eventName: "openat",
29+
expectedError: nil,
30+
shouldFind: true,
31+
eventType: "syscall",
32+
},
33+
{
34+
name: "built-in event - sched_process_exec",
35+
eventName: "sched_process_exec",
36+
expectedError: nil,
37+
shouldFind: true,
38+
eventType: "builtin",
39+
},
40+
{
41+
name: "non-existent event",
42+
eventName: "non_existent_event_12345",
43+
expectedError: nil,
44+
shouldFind: false,
45+
eventType: "",
46+
},
47+
}
48+
49+
// Add signature event test case if signatures are available
50+
if signaturesLoaded {
51+
testCases = append(testCases, struct {
52+
name string
53+
eventName string
54+
expectedError error
55+
shouldFind bool
56+
eventType string
57+
}{
58+
name: "signature event - dropped_executable",
59+
eventName: "dropped_executable",
60+
expectedError: nil,
61+
shouldFind: true,
62+
eventType: "signature",
63+
})
64+
}
65+
66+
for _, tc := range testCases {
67+
tc := tc
68+
t.Run(tc.name, func(t *testing.T) {
69+
// Test if the event can be found
70+
eventID, found := events.Core.GetDefinitionIDByName(tc.eventName)
71+
72+
if tc.shouldFind {
73+
assert.True(t, found, "Event %s should be found", tc.eventName)
74+
assert.NotEqual(t, events.Undefined, eventID, "Event ID should not be undefined")
75+
76+
// Get the definition and check its type
77+
definition := events.Core.GetDefinitionByID(eventID)
78+
79+
switch tc.eventType {
80+
case "syscall":
81+
assert.True(t, definition.IsSyscall(), "Event %s should be a syscall", tc.eventName)
82+
case "signature":
83+
assert.True(t, definition.IsSignature(), "Event %s should be a signature", tc.eventName)
84+
case "builtin":
85+
assert.False(t, definition.IsSyscall(), "Event %s should not be a syscall", tc.eventName)
86+
assert.False(t, definition.IsSignature(), "Event %s should not be a signature", tc.eventName)
87+
}
88+
89+
// Test that the event has a name and description
90+
assert.NotEmpty(t, definition.GetName(), "Event should have a name")
91+
// Note: Some events might have empty descriptions, so we don't assert on that
92+
} else {
93+
assert.False(t, found, "Event %s should not be found", tc.eventName)
94+
}
95+
96+
// Test the actual showEventDocumentation function by capturing output
97+
output := captureOutput(func() {
98+
err := showEventDocumentation(tc.eventName)
99+
assert.Equal(t, tc.expectedError, err, "showEventDocumentation should return expected error")
100+
})
101+
102+
// Verify output contains expected content for found events
103+
if tc.shouldFind {
104+
assert.NotEmpty(t, output, "Should produce output for found events")
105+
if tc.eventType == "syscall" {
106+
assert.Contains(t, output, "Type: System call", "Syscall events should show correct type")
107+
}
108+
} else {
109+
assert.Contains(t, output, "not found", "Should show 'not found' message for missing events")
110+
}
111+
})
112+
}
113+
}
114+
115+
func TestShowEventDocumentationOutput(t *testing.T) {
116+
// Use the shared signature loading from man.go
117+
ensureSignaturesLoaded()
118+
119+
// Test cases for events that should always be available
120+
alwaysAvailableTests := []struct {
121+
name string
122+
eventName string
123+
expectedOutput []string
124+
}{
125+
{
126+
name: "syscall event output format",
127+
eventName: "openat",
128+
expectedOutput: []string{
129+
"Event: openat",
130+
"Type: System call",
131+
"man 2 openat",
132+
},
133+
},
134+
{
135+
name: "non-existent event output",
136+
eventName: "non_existent_event_12345",
137+
expectedOutput: []string{
138+
"not found",
139+
"tracee list",
140+
},
141+
},
142+
}
143+
144+
for _, tc := range alwaysAvailableTests {
145+
tc := tc
146+
t.Run(tc.name, func(t *testing.T) {
147+
output := captureOutput(func() {
148+
err := showEventDocumentation(tc.eventName)
149+
assert.NoError(t, err, "showEventDocumentation should not return error")
150+
})
151+
152+
for _, expected := range tc.expectedOutput {
153+
assert.Contains(t, output, expected, "Output should contain expected text: %s", expected)
154+
}
155+
})
156+
}
157+
158+
// Test signature events only if signatures are available
159+
if signaturesLoaded {
160+
t.Run("signature event output format", func(t *testing.T) {
161+
eventName := "dropped_executable"
162+
expectedOutput := []string{
163+
"TRACEE-DROPPED-EXECUTABLE",
164+
}
165+
166+
_, found := events.Core.GetDefinitionIDByName(eventName)
167+
if !found {
168+
t.Skip("Signature event not available in test environment")
169+
return
170+
}
171+
172+
output := captureOutput(func() {
173+
err := showEventDocumentation(eventName)
174+
assert.NoError(t, err, "showEventDocumentation should not return error")
175+
})
176+
177+
for _, expected := range expectedOutput {
178+
assert.Contains(t, output, expected, "Output should contain expected text: %s", expected)
179+
}
180+
})
181+
} else {
182+
t.Run("signature event not available", func(t *testing.T) {
183+
eventName := "dropped_executable"
184+
output := captureOutput(func() {
185+
err := showEventDocumentation(eventName)
186+
assert.NoError(t, err, "showEventDocumentation should not return error")
187+
})
188+
189+
assert.Contains(t, output, "not found", "Should show 'not found' for unavailable signature events")
190+
})
191+
}
192+
}
193+
194+
func TestEventTypesClassification(t *testing.T) {
195+
// Use the shared signature loading from man.go
196+
ensureSignaturesLoaded()
197+
198+
testCases := []struct {
199+
name string
200+
eventName string
201+
checkFunc func(events.Definition) bool
202+
expected bool
203+
}{
204+
{
205+
name: "openat is syscall",
206+
eventName: "openat",
207+
checkFunc: func(d events.Definition) bool { return d.IsSyscall() },
208+
expected: true,
209+
},
210+
{
211+
name: "sched_process_exec is not syscall",
212+
eventName: "sched_process_exec",
213+
checkFunc: func(d events.Definition) bool { return d.IsSyscall() },
214+
expected: false,
215+
},
216+
{
217+
name: "sched_process_exec is not signature",
218+
eventName: "sched_process_exec",
219+
checkFunc: func(d events.Definition) bool { return d.IsSignature() },
220+
expected: false,
221+
},
222+
}
223+
224+
for _, tc := range testCases {
225+
tc := tc
226+
t.Run(tc.name, func(t *testing.T) {
227+
eventID, found := events.Core.GetDefinitionIDByName(tc.eventName)
228+
require.True(t, found, "Event %s should be found", tc.eventName)
229+
230+
definition := events.Core.GetDefinitionByID(eventID)
231+
result := tc.checkFunc(definition)
232+
assert.Equal(t, tc.expected, result, "Event %s classification check failed", tc.eventName)
233+
})
234+
}
235+
}
236+
237+
func TestSignatureEventsAvailableAfterLoading(t *testing.T) {
238+
// Use the shared signature loading from man.go
239+
ensureSignaturesLoaded()
240+
241+
if !signaturesLoaded {
242+
t.Skip("Skipping signature test - no signatures available in test environment")
243+
return
244+
}
245+
246+
// Test at least one signature event if available
247+
signatureFound := false
248+
for _, sig := range loadedSignatures {
249+
metadata, err := sig.GetMetadata()
250+
if err != nil {
251+
continue
252+
}
253+
254+
eventID, found := events.Core.GetDefinitionIDByName(metadata.EventName)
255+
if found {
256+
definition := events.Core.GetDefinitionByID(eventID)
257+
assert.True(t, definition.IsSignature(), "Event %s should be classified as signature", metadata.EventName)
258+
assert.NotEmpty(t, definition.GetName(), "Event %s should have a name", metadata.EventName)
259+
signatureFound = true
260+
break // Test at least one signature event
261+
}
262+
}
263+
264+
if !signatureFound {
265+
t.Skip("No signature events found to test")
266+
}
267+
}
268+
269+
// TestManCommandIntegration tests the man command functionality with signature loading
270+
func TestManCommandIntegration(t *testing.T) {
271+
// Use the shared signature loading from man.go
272+
ensureSignaturesLoaded()
273+
274+
// Test cases that should always work (built-in events)
275+
builtInTests := []struct {
276+
name string
277+
eventName string
278+
eventType string
279+
}{
280+
{"syscall event", "openat", "syscall"},
281+
{"built-in event", "sched_process_exec", "builtin"},
282+
{"built-in event", "security_file_open", "builtin"},
283+
}
284+
285+
for _, tc := range builtInTests {
286+
tc := tc
287+
t.Run(tc.name, func(t *testing.T) {
288+
eventID, found := events.Core.GetDefinitionIDByName(tc.eventName)
289+
assert.True(t, found, "Built-in event %s should always be available", tc.eventName)
290+
291+
if found {
292+
definition := events.Core.GetDefinitionByID(eventID)
293+
assert.NotEmpty(t, definition.GetName(), "Event should have a name")
294+
295+
// Test the man function doesn't error and captures output properly
296+
output := captureOutput(func() {
297+
err := showEventDocumentation(tc.eventName)
298+
assert.NoError(t, err, "showEventDocumentation should work for built-in events")
299+
})
300+
assert.NotEmpty(t, output, "Should produce documentation output")
301+
}
302+
})
303+
}
304+
305+
// Test signature events if available
306+
if signaturesLoaded && len(loadedSignatures) > 0 {
307+
t.Run("signature event - first available", func(t *testing.T) {
308+
// Find first available signature event
309+
for _, sig := range loadedSignatures {
310+
metadata, err := sig.GetMetadata()
311+
if err != nil {
312+
continue
313+
}
314+
315+
eventID, found := events.Core.GetDefinitionIDByName(metadata.EventName)
316+
if found {
317+
definition := events.Core.GetDefinitionByID(eventID)
318+
assert.True(t, definition.IsSignature(), "Event should be classified as signature")
319+
assert.NotEmpty(t, definition.GetName(), "Event should have a name")
320+
321+
// Test the man function works for signature events
322+
output := captureOutput(func() {
323+
err := showEventDocumentation(metadata.EventName)
324+
assert.NoError(t, err, "showEventDocumentation should work for signature events")
325+
})
326+
assert.NotEmpty(t, output, "Should produce documentation output for signature events")
327+
break // Test one signature event
328+
}
329+
}
330+
})
331+
}
332+
}
333+
334+
// captureOutput captures stdout during function execution
335+
func captureOutput(f func()) string {
336+
old := os.Stdout
337+
r, w, err := os.Pipe()
338+
if err != nil {
339+
panic("Failed to create pipe for test output capture: " + err.Error())
340+
}
341+
os.Stdout = w
342+
343+
f()
344+
345+
w.Close()
346+
os.Stdout = old
347+
348+
var buf bytes.Buffer
349+
_, err = io.Copy(&buf, r)
350+
if err != nil {
351+
panic("Failed to read captured output: " + err.Error())
352+
}
353+
return buf.String()
354+
}
355+
356+
// TestEventDefinitionRequirements tests that event definitions meet basic requirements
357+
func TestEventDefinitionRequirements(t *testing.T) {
358+
allDefinitions := events.Core.GetDefinitions()
359+
360+
// Ensure we have some core events
361+
assert.Greater(t, len(allDefinitions), 10, "Should have at least some core events")
362+
363+
// Test a few key events exist
364+
keyEvents := []string{"openat", "execve", "close", "read", "write"}
365+
366+
for _, eventName := range keyEvents {
367+
t.Run("event_"+eventName, func(t *testing.T) {
368+
eventID, found := events.Core.GetDefinitionIDByName(eventName)
369+
assert.True(t, found, "Key event %s should be available", eventName)
370+
371+
if found {
372+
definition := events.Core.GetDefinitionByID(eventID)
373+
assert.Equal(t, eventName, definition.GetName(), "Event name should match")
374+
assert.NotEqual(t, events.Undefined, definition.GetID(), "Event should have valid ID")
375+
}
376+
})
377+
}
378+
}

0 commit comments

Comments
 (0)