github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/snapshot/process/tracer/tracer_test.go (about)

     1  // Copyright 2022-2023 The Inspektor Gadget authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build linux
    16  // +build linux
    17  
    18  package tracer
    19  
    20  import (
    21  	"fmt"
    22  	"os"
    23  	"os/exec"
    24  	"reflect"
    25  	"testing"
    26  
    27  	utilstest "github.com/inspektor-gadget/inspektor-gadget/internal/test"
    28  	containerutils "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils"
    29  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets"
    30  	snapshotProcessTypes "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/snapshot/process/types"
    31  	eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types"
    32  )
    33  
    34  type collectorFunc func(config *Config, enricher gadgets.DataEnricherByMntNs) ([]*snapshotProcessTypes.Event, error)
    35  
    36  func BenchmarkSnapshotProcessEBPFTracer(b *testing.B) {
    37  	benchmarkTracer(b, runeBPFCollector)
    38  }
    39  
    40  func BenchmarkSnapshotProcessProcfsTracer(b *testing.B) {
    41  	benchmarkTracer(b, runProcfsCollector)
    42  }
    43  
    44  func benchmarkTracer(b *testing.B, runCollector collectorFunc) {
    45  	utilstest.RequireRoot(b)
    46  
    47  	for n := 0; n < b.N; n++ {
    48  		_, err := runCollector(&Config{}, nil)
    49  		if err != nil {
    50  			b.Fatalf("benchmarking collector: %s", err)
    51  		}
    52  	}
    53  }
    54  
    55  func TestSnapshotProcessEBPFTracer(t *testing.T) {
    56  	testTracer(t, runeBPFCollector)
    57  }
    58  
    59  func TestSnapshotProcessProcfsTracer(t *testing.T) {
    60  	testTracer(t, runProcfsCollector)
    61  }
    62  
    63  func testTracer(t *testing.T, runCollector collectorFunc) {
    64  	t.Parallel()
    65  
    66  	utilstest.RequireRoot(t)
    67  	utilstest.HostInit(t)
    68  
    69  	type testDefinition struct {
    70  		getTracerConfig func(info *utilstest.RunnerInfo) *Config
    71  		runnerConfig    *utilstest.RunnerConfig
    72  		generateEvent   func() (int, error)
    73  		validateEvent   func(t *testing.T, info *utilstest.RunnerInfo, sleepPid int, events []snapshotProcessTypes.Event)
    74  	}
    75  
    76  	for name, test := range map[string]testDefinition{
    77  		"captures_all_events_with_no_filters_configured": {
    78  			getTracerConfig: func(info *utilstest.RunnerInfo) *Config {
    79  				return &Config{}
    80  			},
    81  			generateEvent: generateEvent,
    82  			validateEvent: utilstest.ExpectAtLeastOneEvent(func(info *utilstest.RunnerInfo, sleepPid int) *snapshotProcessTypes.Event {
    83  				return &snapshotProcessTypes.Event{
    84  					Event: eventtypes.Event{
    85  						Type: eventtypes.NORMAL,
    86  					},
    87  					Command:       "sleep",
    88  					Pid:           sleepPid,
    89  					Tid:           sleepPid,
    90  					ParentPid:     0,
    91  					WithMountNsID: eventtypes.WithMountNsID{MountNsID: info.MountNsID},
    92  				}
    93  			}),
    94  		},
    95  		"captures_no_events_with_no_matching_filter": {
    96  			getTracerConfig: func(info *utilstest.RunnerInfo) *Config {
    97  				// We can't use 0 as the mntnsid because the tracer will collect
    98  				// some defunct processes that don't have any mntnsid defined
    99  				// anymore. Then, we set the network namespace inode id to be sure
   100  				// there is not any mount namespace using that same inode.
   101  				mntns, _ := containerutils.GetNetNs(os.Getpid())
   102  				return &Config{
   103  					MountnsMap: utilstest.CreateMntNsFilterMap(t, mntns),
   104  				}
   105  			},
   106  			generateEvent: generateEvent,
   107  			validateEvent: utilstest.ExpectNoEvent[snapshotProcessTypes.Event, int],
   108  		},
   109  		"captures_events_with_matching_filter": {
   110  			getTracerConfig: func(info *utilstest.RunnerInfo) *Config {
   111  				return &Config{
   112  					MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID),
   113  				}
   114  			},
   115  			generateEvent: generateEvent,
   116  			// We have to use ExpectAtLeastOneEvent because it's possible that the
   117  			// golang thread that executes this test is also captured
   118  			validateEvent: utilstest.ExpectAtLeastOneEvent(func(info *utilstest.RunnerInfo, sleepPid int) *snapshotProcessTypes.Event {
   119  				return &snapshotProcessTypes.Event{
   120  					Event: eventtypes.Event{
   121  						Type: eventtypes.NORMAL,
   122  					},
   123  					Command:       "sleep",
   124  					Pid:           sleepPid,
   125  					Tid:           sleepPid,
   126  					WithMountNsID: eventtypes.WithMountNsID{MountNsID: info.MountNsID},
   127  				}
   128  			}),
   129  		},
   130  		// This is a hacky way to test this: one of the threads of the goroutine is moved to
   131  		// the mount namespace created for testing, also the sleep process we execute is
   132  		// there. That's why 2 events are expected. A better way would be to execute a
   133  		// command that creates multiple threads and check if we capture all of them, but so
   134  		// far I haven't found an easy way to do so. One idea is to use python but it seems
   135  		// too complicated and will introduce another dependency for testing.
   136  		"captures_events_with_matching_filter_threads": {
   137  			getTracerConfig: func(info *utilstest.RunnerInfo) *Config {
   138  				return &Config{
   139  					MountnsMap:  utilstest.CreateMntNsFilterMap(t, info.MountNsID),
   140  					ShowThreads: true,
   141  				}
   142  			},
   143  			generateEvent: generateEvent,
   144  			validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, sleepPid int, events []snapshotProcessTypes.Event) {
   145  				if len(events) != 2 {
   146  					t.Fatalf("%d events expected, found: %d", 2, len(events))
   147  				}
   148  
   149  				expectedEvent := &snapshotProcessTypes.Event{
   150  					Event: eventtypes.Event{
   151  						Type: eventtypes.NORMAL,
   152  					},
   153  					Command:       "sleep",
   154  					Pid:           sleepPid,
   155  					Tid:           sleepPid,
   156  					ParentPid:     0,
   157  					WithMountNsID: eventtypes.WithMountNsID{MountNsID: info.MountNsID},
   158  				}
   159  
   160  				for _, event := range events {
   161  					if reflect.DeepEqual(expectedEvent, &event) {
   162  						return
   163  					}
   164  				}
   165  
   166  				t.Fatalf("Event wasn't captured")
   167  			},
   168  		},
   169  		"no_threads_are_captured": {
   170  			getTracerConfig: func(info *utilstest.RunnerInfo) *Config {
   171  				return &Config{}
   172  			},
   173  			generateEvent: generateEvent,
   174  			validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, sleepPid int, events []snapshotProcessTypes.Event) {
   175  				if len(events) == 0 {
   176  					t.Fatalf("no events were captured")
   177  				}
   178  				for _, event := range events {
   179  					if event.Pid != event.Tid {
   180  						t.Fatalf("thread %d was captured", event.Tid)
   181  					}
   182  				}
   183  			},
   184  		},
   185  	} {
   186  		test := test
   187  
   188  		t.Run(name, func(t *testing.T) {
   189  			t.Parallel()
   190  
   191  			runner := utilstest.NewRunnerWithTest(t, test.runnerConfig)
   192  
   193  			var sleepPid int
   194  
   195  			utilstest.RunWithRunner(t, runner, func() error {
   196  				var err error
   197  				sleepPid, err = test.generateEvent()
   198  				return err
   199  			})
   200  
   201  			events, err := runCollector(test.getTracerConfig(runner.Info), nil)
   202  			if err != nil {
   203  				t.Fatalf("running collector: %s", err)
   204  			}
   205  
   206  			// TODO: This won't be required once we pass pointers everywhere
   207  			validateEvents := []snapshotProcessTypes.Event{}
   208  			for _, event := range events {
   209  				// Normalize parent PID to avoid failing tests as this is not trivial to
   210  				// guess the parent PID.
   211  				event.ParentPid = 0
   212  
   213  				validateEvents = append(validateEvents, *event)
   214  			}
   215  
   216  			test.validateEvent(t, runner.Info, sleepPid, validateEvents)
   217  		})
   218  	}
   219  }
   220  
   221  // Function that runs a "sleep" process.
   222  func generateEvent() (int, error) {
   223  	cmd := exec.Command("/bin/sleep", "5")
   224  	if err := cmd.Start(); err != nil {
   225  		return 0, fmt.Errorf("running command: %w", err)
   226  	}
   227  
   228  	return cmd.Process.Pid, nil
   229  }