From 3f0b191f919125b6ee6a8e6e8ea7bd71eb1b9e3f Mon Sep 17 00:00:00 2001 From: mastersans Date: Mon, 16 Dec 2024 23:43:24 +0530 Subject: [PATCH] feat: added sample unit and integration tests for template Signed-off-by: mastersans --- README.md | 1 + test/integration/changeme_mytracer_test.go | 89 +++++++++++++++++++++ test/unit/changeme_mytracer_test.go | 90 ++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 test/integration/changeme_mytracer_test.go create mode 100644 test/unit/changeme_mytracer_test.go diff --git a/README.md b/README.md index 968f9ca..81bfdeb 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Steps to use this template: - Click on *Create repository* - Update the placeholders (`git grep -i CHANGEME`, `git grep -i TODO`) - Write your eBPF program (follow [Hello world gadget](https://www.inspektor-gadget.io/docs/latest/gadget-devel/hello-world-gadget)) +- Write [Unit](https://www.inspektor-gadget.io/blog/2024/12/inspektor-gadget-unittesting-framework) and [Integration](https://www.inspektor-gadget.io/blog/2024/06/introducing-the-new-testing-framework-for-image-based-gadgets) Tests for Gadget. - Delete this section from README.md --- diff --git a/test/integration/changeme_mytracer_test.go b/test/integration/changeme_mytracer_test.go new file mode 100644 index 0000000..b2e87a5 --- /dev/null +++ b/test/integration/changeme_mytracer_test.go @@ -0,0 +1,89 @@ +// This is a sample integration test file to serve as a guide for implementation. +// Refer to this blog: https://www.inspektor-gadget.io/blog/2024/06/introducing-the-new-testing-framework-for-image-based-gadgets. + +package tests + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + gadgettesting "github.com/inspektor-gadget/inspektor-gadget/gadgets/testing" + ebpftypes "github.com/inspektor-gadget/inspektor-gadget/pkg/operators/ebpf/types" + igtesting "github.com/inspektor-gadget/inspektor-gadget/pkg/testing" + "github.com/inspektor-gadget/inspektor-gadget/pkg/testing/containers" + igrunner "github.com/inspektor-gadget/inspektor-gadget/pkg/testing/ig" + "github.com/inspektor-gadget/inspektor-gadget/pkg/testing/match" + "github.com/inspektor-gadget/inspektor-gadget/pkg/testing/utils" +) + +// ExpectedMyTracerEvent represents the structure of the expected output +// from the tracer. It helps in validating the actual output. +type ExpectedMyTracerEvent struct { + Proc ebpftypes.Process `json:"proc"` + Fd uint32 `json:"fd"` + FName string `json:"fname"` + // Add additional expected fields as necessary +} + +func TestMyTracerGadget(t *testing.T) { + // Ensure required environment variables are set; skip test otherwise. + gadgettesting.RequireEnvironmentVariables(t) + // Initialize utilities for the test. + utils.InitTest(t) + + // Create a container factory to manage container lifecycle during the test. + containerFactory, err := containers.NewContainerFactory(utils.Runtime) + require.NoError(t, err, "Failed to create a new container factory") + + // Define the test container's name and image. + containerName := "test-mytracer" + containerImage := "base-container-image" // Replace with the actual container image to be used. + + // Configure the test container with the desired command and options. + testContainer := containerFactory.NewContainer( + containerName, + "desired command for execution", // Replace with the actual command to execute in the container. + containers.WithContainerImage(containerImage), + ) + + // Start the test container and defer its cleanup. + testContainer.Start(t) + t.Cleanup(func() { + testContainer.Stop(t) + }) + + // Configure options for running the tracer gadget. + runnerOpts := []igrunner.Option{ + // Specify runtime and set a timeout for the tracer execution. + igrunner.WithFlags(fmt.Sprintf("-r=%s", utils.Runtime), "--timeout=5"), + + // Provide a validation function to check the tracer's output against expectations. + igrunner.WithValidateOutput(func(t *testing.T, output string) { + // Define the expected event structure. + expectedEntry := &ExpectedMyTracerEvent{ + Proc: utils.BuildProc("cat", 1000, 1111), + FName: "/dev/null", + Fd: 3, + // Add additional expected fields as necessary. + } + + // Define a normalization function if any field requires preprocessing before comparison. + normalize := func(e *ExpectedMyTracerEvent) { + // Example: Normalize fields such as timestamps or dynamic values. + } + + // Match actual output with the expected entry. + match.MatchEntries(t, match.JSONMultiObjectMode, output, normalize, expectedEntry) + }), + } + + // Define the test steps to run the tracer gadget. + testSteps := []igtesting.TestStep{ + igrunner.New("mytracer", runnerOpts...), // Replace "mytracer" with the actual gadget name. + } + + // Execute the test steps. + igtesting.RunTestSteps(testSteps, t, nil) +} diff --git a/test/unit/changeme_mytracer_test.go b/test/unit/changeme_mytracer_test.go new file mode 100644 index 0000000..59a5fef --- /dev/null +++ b/test/unit/changeme_mytracer_test.go @@ -0,0 +1,90 @@ +// This is a sample unit test file to serve as a guide for implementation. +// Refer to this blog: https://www.inspektor-gadget.io/blog/2024/12/inspektor-gadget-unittesting-framework + +package tests + +import ( + "testing" + + "github.com/cilium/ebpf" + + gadgettesting "github.com/inspektor-gadget/inspektor-gadget/gadgets/testing" + utilstest "github.com/inspektor-gadget/inspektor-gadget/internal/test" + ebpftypes "github.com/inspektor-gadget/inspektor-gadget/pkg/operators/ebpf/types" + "github.com/inspektor-gadget/inspektor-gadget/pkg/testing/gadgetrunner" +) + +// ExpectedMyTracerEvent represents the structure of the expected output +// from the tracer. It helps in validating the actual output. +type ExpectedMyTracerEvent struct { + Proc ebpftypes.Process `json:"proc"` + Fd uint32 `json:"fd"` + FName string `json:"fname"` + // Add additional expected fields as necessary +} + +type testDef struct { + // Configuration for initializing the runner + runnerConfig *utilstest.RunnerConfig + + // Function to create a mount namespace filter map + mntnsFilterMap func(info *utilstest.RunnerInfo) *ebpf.Map + + // Function to generate events in the gadget + generateEvent func() (int, error) + + // Validation logic for the captured events + validateEvent func(t *testing.T, info *utilstest.RunnerInfo, fd int, events []ExpectedMyTracerEvent) +} + +func TestMyTracerGadget(t *testing.T) { + // Initialize the unit test + gadgettesting.InitUnitTest(t) + + // Define test cases + testCases := map[string]testDef{ + "ExampleTest": { + runnerConfig: &utilstest.RunnerConfig{ + // Add necessary runner configurations here + }, + mntnsFilterMap: func(info *utilstest.RunnerInfo) *ebpf.Map { + // Example of creating an eBPF map (replace with actual implementation) + return nil + }, + generateEvent: func() (int, error) { + // Simulate the generation of events (replace with actual logic) + return 0, nil + }, + validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, fd int, events []ExpectedMyTracerEvent) { + // Add logic to validate captured events against expected events + if len(events) == 0 { + t.Errorf("No events captured") + } + }, + }, + // Add more test cases as needed + } + + // Iterate over test cases + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + // Initialize the runner + runner := utilstest.NewRunnerWithTest(t, testCase.runnerConfig) + defer runner.Close() + + // Configure gadget runner options + opts := gadgetrunner.GadgetRunnerOpts[ExpectedMyTracerEvent]{ + // Set up options such as gadget name, arguments, etc. + } + + // Initialize and run the gadget + gadgetRunner := gadgetrunner.NewGadgetRunner(t, opts) + gadgetRunner.RunGadget() + + // Validate captured events + testCase.validateEvent(t, runner.Info, 0, gadgetRunner.CapturedEvents) + }) + } +}