github.com/castai/kvisor@v1.7.1-0.20240516114728-b3572a2607b5/pkg/proc/proc_test.go (about) 1 package proc 2 3 import ( 4 "fmt" 5 "syscall" 6 "testing" 7 "testing/fstest" 8 "time" 9 10 "github.com/stretchr/testify/require" 11 ) 12 13 func TestLoadMountNSOldestProcesses(t *testing.T) { 14 t.Run("should return process if only one in namespace", func(t *testing.T) { 15 r := require.New(t) 16 17 wantedPID := PID(12) 18 wantedMountNSID := NamespaceID(10) 19 20 procFS := fstest.MapFS{} 21 procFS[fmt.Sprintf("%d/stat", wantedPID)] = buildStatMapFile(0) 22 procFS[fmt.Sprintf("%d/ns/mnt", wantedPID)] = buildNamespaceMapFile(wantedMountNSID) 23 24 procFS["99/stat"] = buildStatMapFile(99) 25 procFS["99/ns/mnt"] = buildNamespaceMapFile(99) 26 27 proc := Proc{procFS: procFS} 28 29 result, err := proc.LoadMountNSOldestProcesses() 30 if err != nil { 31 r.NoError(err) 32 } 33 34 r.Equal(wantedPID, result[wantedMountNSID]) 35 }) 36 37 t.Run("should return oldest process if multiple in namespace", func(t *testing.T) { 38 r := require.New(t) 39 40 oldestPID := PID(12) 41 mountNSID := NamespaceID(10) 42 43 procFS := fstest.MapFS{} 44 procFS[fmt.Sprintf("%d/stat", oldestPID)] = buildStatMapFile(0) 45 procFS[fmt.Sprintf("%d/ns/mnt", oldestPID)] = buildNamespaceMapFile(mountNSID) 46 47 procFS["99/stat"] = buildStatMapFile(99) 48 procFS["99/ns/mnt"] = buildNamespaceMapFile(mountNSID) 49 50 proc := Proc{procFS: procFS} 51 52 result, err := proc.LoadMountNSOldestProcesses() 53 if err != nil { 54 r.NoError(err) 55 } 56 57 r.Equal(oldestPID, result[mountNSID]) 58 }) 59 60 t.Run("should not error if namespaces does not exist for one process", func(t *testing.T) { 61 r := require.New(t) 62 63 wantedPID := PID(12) 64 wantedNSID := NamespaceID(10) 65 66 procFS := fstest.MapFS{} 67 procFS[fmt.Sprintf("%d/stat", wantedPID)] = buildStatMapFile(0) 68 procFS[fmt.Sprintf("%d/ns/mnt", wantedPID)] = buildNamespaceMapFile(wantedNSID) 69 70 procFS["99/stat"] = buildStatMapFile(99) 71 72 proc := Proc{procFS: procFS} 73 74 result, err := proc.LoadMountNSOldestProcesses() 75 if err != nil { 76 r.NoError(err) 77 } 78 79 r.Equal(wantedPID, result[wantedNSID]) 80 }) 81 } 82 83 func buildStatMapFile(age int) *fstest.MapFile { 84 return &fstest.MapFile{ 85 Data: []byte(fmt.Sprintf("1 (systemd) S 0 1 1 0 -1 4194560 49750 11902233 171 14858 475 453 49354 59480 20 0 1 0 %d 172085248 2208 18446744073709551615 1 1 0 0 0 0 671173123 4096 1260 0 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0", age)), 86 Mode: 0666, 87 ModTime: time.Now(), 88 } 89 } 90 91 func buildNamespaceMapFile(id NamespaceID) *fstest.MapFile { 92 return &fstest.MapFile{ 93 Data: []byte{}, 94 Mode: 0666, 95 ModTime: time.Now(), 96 Sys: &syscall.Stat_t{ 97 Ino: uint64(id), 98 }, 99 } 100 } 101 102 func TestGetNSForPID(t *testing.T) { 103 type testCase struct { 104 title string 105 namespaces map[NamespaceType]uint64 106 wantedNamespace NamespaceType 107 expectedID uint64 108 expectError bool 109 } 110 111 pidNSID := uint64(20) 112 mntNSID := uint64(90) 113 114 testCases := []testCase{ 115 { 116 title: "should return requested namespace id", 117 namespaces: map[NamespaceType]uint64{ 118 MountNamespace: mntNSID, 119 PIDNamespace: pidNSID, 120 }, 121 wantedNamespace: MountNamespace, 122 expectedID: mntNSID, 123 }, 124 { 125 title: "should fail when requesting unknown namespace type", 126 namespaces: map[NamespaceType]uint64{ 127 MountNamespace: mntNSID, 128 PIDNamespace: pidNSID, 129 }, 130 wantedNamespace: "non existing", 131 expectError: true, 132 }, 133 } 134 testPID := PID(22) 135 136 for _, test := range testCases { 137 t.Run(test.title, func(t *testing.T) { 138 r := require.New(t) 139 140 procFS := buildTestNSProcFS(testPID, test.namespaces) 141 proc := Proc{procFS: procFS} 142 143 result, err := proc.GetNSForPID(testPID, test.wantedNamespace) 144 if test.expectError { 145 r.Error(err) 146 return 147 } else { 148 r.NoError(err) 149 } 150 151 r.Equal(test.expectedID, result) 152 }) 153 } 154 } 155 156 func buildTestNSProcFS(pid PID, namespaces map[NamespaceType]uint64) ProcFS { 157 if namespaces == nil { 158 return fstest.MapFS{} 159 } 160 161 result := fstest.MapFS{} 162 163 for namespace, id := range namespaces { 164 result[fmt.Sprintf("%d/ns/%s", pid, namespace)] = &fstest.MapFile{ 165 Data: []byte{}, 166 Mode: 0666, 167 ModTime: time.Now(), 168 Sys: &syscall.Stat_t{ 169 Ino: id, 170 }, 171 } 172 } 173 174 return result 175 } 176 177 func TestGetProcessStartTime(t *testing.T) { 178 type testCase struct { 179 title string 180 statFileData []byte 181 expectedStartTime uint64 182 expectError bool 183 } 184 185 testCases := []testCase{ 186 { 187 title: "parse simple stat file", 188 statFileData: []byte("1 (systemd) S 0 1 1 0 -1 4194560 49750 11902233 171 14858 475 453 49354 59480 20 0 1 0 0 172085248 2208 18446744073709551615 1 1 0 0 0 0 671173123 4096 1260 0 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0"), 189 expectedStartTime: 0, 190 }, 191 { 192 title: "parse simple stat file with start time", 193 statFileData: []byte("1 (bash) S 0 1 1 0 -1 4194560 49750 11902233 171 14858 475 453 49354 59480 20 0 1 0 20 172085248 2208 18446744073709551615 1 1 0 0 0 0 671173123 4096 1260 0 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0"), 194 expectedStartTime: 20, 195 }, 196 { 197 title: "parse simple stat file with space in comm", 198 statFileData: []byte("1 (bash 1) S 0 1 1 0 -1 4194560 49750 11902233 171 14858 475 453 49354 59480 20 0 1 0 20 172085248 2208 18446744073709551615 1 1 0 0 0 0 671173123 4096 1260 0 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0"), 199 expectedStartTime: 20, 200 }, 201 { 202 title: "parse simple stat file with extremely long runtime", 203 statFileData: []byte("1 (bash 1) S 0 1 1 0 -1 4194560 49750 11902233 171 14858 475 453 49354 59480 20 0 1 0 99999999 172085248 2208 18446744073709551615 1 1 0 0 0 0 671173123 4096 1260 0 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0"), 204 expectedStartTime: 99999999, 205 }, 206 { 207 title: "should fail with error if stat file is missing", 208 expectError: true, 209 }, 210 { 211 title: "should fail with error if stat file does not have enough fields", 212 statFileData: []byte("1 (bash 1) S 0 1 1 0 "), 213 expectError: true, 214 }, 215 } 216 testPID := PID(22) 217 218 for _, test := range testCases { 219 t.Run(test.title, func(t *testing.T) { 220 r := require.New(t) 221 222 procFS := buildTestProcStatFileFS(testPID, test.statFileData) 223 proc := Proc{procFS: procFS} 224 225 result, err := proc.GetProcessStartTime(testPID) 226 if test.expectError { 227 r.Error(err) 228 return 229 } else { 230 r.NoError(err) 231 } 232 233 r.Equal(test.expectedStartTime, result) 234 }) 235 } 236 } 237 238 func buildTestProcStatFileFS(pid PID, statFileData []byte) ProcFS { 239 if statFileData == nil { 240 return fstest.MapFS{} 241 } 242 243 return fstest.MapFS{ 244 fmt.Sprintf("%d/stat", pid): &fstest.MapFile{ 245 Data: statFileData, 246 Mode: 0666, 247 ModTime: time.Now(), 248 }, 249 } 250 }