github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/mapfs/fs_test.go (about) 1 package mapfs_test 2 3 import ( 4 "io" 5 "io/fs" 6 "runtime" 7 "testing" 8 9 "github.com/samber/lo" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 13 "github.com/devseccon/trivy/pkg/mapfs" 14 ) 15 16 type fileInfo struct { 17 name string 18 fileMode fs.FileMode 19 isDir bool 20 size int64 21 } 22 23 var ( 24 filePerm = lo.Ternary(runtime.GOOS == "windows", fs.FileMode(0666), fs.FileMode(0644)) 25 helloFileInfo = fileInfo{ 26 name: "hello.txt", 27 fileMode: filePerm, 28 isDir: false, 29 size: 11, 30 } 31 btxtFileInfo = fileInfo{ 32 name: "b.txt", 33 fileMode: filePerm, 34 isDir: false, 35 size: 3, 36 } 37 virtualFileInfo = fileInfo{ 38 name: "virtual.txt", 39 fileMode: 0600, 40 isDir: false, 41 size: 7, 42 } 43 cdirFileInfo = fileInfo{ 44 name: "c", 45 fileMode: fs.FileMode(0700) | fs.ModeDir, 46 isDir: true, 47 size: 256, 48 } 49 ) 50 51 func initFS(t *testing.T) *mapfs.FS { 52 fsys := mapfs.New() 53 require.NoError(t, fsys.MkdirAll("a/b/c", 0700)) 54 require.NoError(t, fsys.MkdirAll("a/b/empty", 0700)) 55 require.NoError(t, fsys.WriteFile("hello.txt", "testdata/hello.txt")) 56 require.NoError(t, fsys.WriteFile("a/b/b.txt", "testdata/b.txt")) 57 require.NoError(t, fsys.WriteFile("a/b/c/c.txt", "testdata/c.txt")) 58 require.NoError(t, fsys.WriteFile("a/b/c/.dotfile", "testdata/dotfile")) 59 require.NoError(t, fsys.WriteVirtualFile("a/b/c/virtual.txt", []byte("virtual"), 0600)) 60 return fsys 61 } 62 63 func assertFileInfo(t *testing.T, want fileInfo, got fs.FileInfo) { 64 if got == nil { 65 return 66 } 67 assert.Equal(t, want.name, got.Name()) 68 assert.Equal(t, want.fileMode, got.Mode()) 69 assert.Equal(t, want.isDir, got.Mode().IsDir()) 70 assert.Equal(t, want.isDir, got.IsDir()) 71 assert.Equal(t, want.size, got.Size()) 72 } 73 74 func TestFS_Filter(t *testing.T) { 75 fsys := initFS(t) 76 t.Run("empty files", func(t *testing.T) { 77 newFS, err := fsys.Filter(nil) 78 require.NoError(t, err) 79 assert.Equal(t, fsys, newFS) 80 }) 81 t.Run("happy", func(t *testing.T) { 82 newFS, err := fsys.Filter([]string{ 83 "hello.txt", 84 "a/b/c/.dotfile", 85 }) 86 require.NoError(t, err) 87 _, err = newFS.Stat("hello.txt") 88 require.ErrorIs(t, err, fs.ErrNotExist) 89 _, err = newFS.Stat("a/b/c/.dotfile") 90 require.ErrorIs(t, err, fs.ErrNotExist) 91 fi, err := newFS.Stat("a/b/c/c.txt") 92 require.NoError(t, err) 93 assert.Equal(t, "c.txt", fi.Name()) 94 }) 95 } 96 97 func TestFS_Stat(t *testing.T) { 98 tests := []struct { 99 name string 100 filePath string 101 want fileInfo 102 wantErr assert.ErrorAssertionFunc 103 }{ 104 { 105 name: "ordinary file", 106 filePath: "hello.txt", 107 want: helloFileInfo, 108 wantErr: assert.NoError, 109 }, 110 { 111 name: "nested file", 112 filePath: "a/b/b.txt", 113 want: btxtFileInfo, 114 wantErr: assert.NoError, 115 }, 116 { 117 name: "virtual file", 118 filePath: "a/b/c/virtual.txt", 119 want: virtualFileInfo, 120 wantErr: assert.NoError, 121 }, 122 { 123 name: "dir", 124 filePath: "a/b/c", 125 want: cdirFileInfo, 126 wantErr: assert.NoError, 127 }, 128 { 129 name: "no such file", 130 filePath: "nosuch.txt", 131 wantErr: assert.Error, 132 }, 133 } 134 135 for _, tt := range tests { 136 fsys := initFS(t) 137 t.Run(tt.name, func(t *testing.T) { 138 got, err := fsys.Stat(tt.filePath) 139 tt.wantErr(t, err) 140 assertFileInfo(t, tt.want, got) 141 }) 142 } 143 } 144 145 func TestFS_ReadDir(t *testing.T) { 146 type dirEntry struct { 147 name string 148 fileMode fs.FileMode 149 isDir bool 150 size int64 151 fileInfo fileInfo 152 } 153 154 tests := []struct { 155 name string 156 filePath string 157 want []dirEntry 158 wantErr assert.ErrorAssertionFunc 159 }{ 160 { 161 name: "at root", 162 filePath: ".", 163 want: []dirEntry{ 164 { 165 name: "a", 166 fileMode: fs.FileMode(0700) | fs.ModeDir, 167 isDir: true, 168 size: 0x100, 169 fileInfo: fileInfo{ 170 name: "a", 171 fileMode: fs.FileMode(0700) | fs.ModeDir, 172 isDir: true, 173 size: 0x100, 174 }, 175 }, 176 { 177 name: "hello.txt", 178 fileMode: filePerm, 179 isDir: false, 180 size: 11, 181 fileInfo: helloFileInfo, 182 }, 183 }, 184 wantErr: assert.NoError, 185 }, 186 { 187 name: "multiple files", 188 filePath: "a/b/c", 189 want: []dirEntry{ 190 { 191 name: ".dotfile", 192 fileMode: filePerm, 193 isDir: false, 194 size: 7, 195 fileInfo: fileInfo{ 196 name: ".dotfile", 197 fileMode: filePerm, 198 isDir: false, 199 size: 7, 200 }, 201 }, 202 { 203 name: "c.txt", 204 fileMode: filePerm, 205 isDir: false, 206 size: 0, 207 fileInfo: fileInfo{ 208 name: "c.txt", 209 fileMode: filePerm, 210 isDir: false, 211 size: 0, 212 }, 213 }, 214 { 215 name: "virtual.txt", 216 fileMode: 0600, 217 isDir: false, 218 size: 0, 219 fileInfo: virtualFileInfo, 220 }, 221 }, 222 wantErr: assert.NoError, 223 }, 224 { 225 name: "no such dir", 226 filePath: "nosuch/", 227 wantErr: assert.Error, 228 }, 229 } 230 231 for _, tt := range tests { 232 fsys := initFS(t) 233 t.Run(tt.name, func(t *testing.T) { 234 entries, err := fsys.ReadDir(tt.filePath) 235 tt.wantErr(t, err) 236 237 for _, z := range lo.Zip2(entries, tt.want) { 238 got, want := z.A, z.B 239 assert.Equal(t, want.name, got.Name()) 240 assert.Equal(t, want.fileMode, got.Type(), want.name) 241 assert.Equal(t, want.isDir, got.IsDir(), want.name) 242 243 fi, err := got.Info() 244 require.NoError(t, err) 245 assertFileInfo(t, want.fileInfo, fi) 246 } 247 }) 248 } 249 } 250 251 func TestFS_Open(t *testing.T) { 252 type file struct { 253 fileInfo fileInfo 254 body string 255 } 256 257 tests := []struct { 258 name string 259 filePath string 260 want file 261 wantErr assert.ErrorAssertionFunc 262 }{ 263 { 264 name: "ordinary file", 265 filePath: "hello.txt", 266 want: file{ 267 fileInfo: helloFileInfo, 268 body: "hello world", 269 }, 270 wantErr: assert.NoError, 271 }, 272 { 273 name: "virtual file", 274 filePath: "a/b/c/virtual.txt", 275 want: file{ 276 fileInfo: virtualFileInfo, 277 body: "virtual", 278 }, 279 wantErr: assert.NoError, 280 }, 281 { 282 name: "dir", 283 filePath: "a/b/c", 284 want: file{ 285 fileInfo: cdirFileInfo, 286 }, 287 wantErr: assert.NoError, 288 }, 289 { 290 name: "no such file", 291 filePath: "nosuch.txt", 292 wantErr: assert.Error, 293 }, 294 } 295 296 for _, tt := range tests { 297 fsys := initFS(t) 298 t.Run(tt.name, func(t *testing.T) { 299 f, err := fsys.Open(tt.filePath) 300 tt.wantErr(t, err) 301 if f == nil { 302 return 303 } 304 defer func() { 305 require.NoError(t, f.Close()) 306 }() 307 308 fi, err := f.Stat() 309 require.NoError(t, err) 310 assertFileInfo(t, tt.want.fileInfo, fi) 311 312 if tt.want.body != "" { 313 b, err := io.ReadAll(f) 314 require.NoError(t, err) 315 assert.Equal(t, tt.want.body, string(b)) 316 } 317 }) 318 } 319 } 320 321 func TestFS_ReadFile(t *testing.T) { 322 tests := []struct { 323 name string 324 filePath string 325 want string 326 wantErr assert.ErrorAssertionFunc 327 }{ 328 { 329 name: "ordinary file", 330 filePath: "hello.txt", 331 want: "hello world", 332 wantErr: assert.NoError, 333 }, 334 { 335 name: "virtual file", 336 filePath: "a/b/c/virtual.txt", 337 want: "virtual", 338 wantErr: assert.NoError, 339 }, 340 { 341 name: "no such file", 342 filePath: "nosuch.txt", 343 wantErr: assert.Error, 344 }, 345 } 346 347 for _, tt := range tests { 348 fsys := initFS(t) 349 t.Run(tt.name, func(t *testing.T) { 350 b, err := fsys.ReadFile(tt.filePath) 351 tt.wantErr(t, err) 352 assert.Equal(t, tt.want, string(b)) 353 }) 354 } 355 } 356 357 func TestFS_Sub(t *testing.T) { 358 fsys := initFS(t) 359 sub, err := fsys.Sub("a/b") 360 require.NoError(t, err) 361 362 data, err := sub.(fs.ReadFileFS).ReadFile("c/.dotfile") 363 require.NoError(t, err) 364 assert.Equal(t, "dotfile", string(data)) 365 } 366 367 func TestFS_Glob(t *testing.T) { 368 tests := []struct { 369 name string 370 pattern string 371 want []string 372 wantErr assert.ErrorAssertionFunc 373 }{ 374 { 375 name: "root", 376 pattern: "*", 377 want: []string{ 378 "a", 379 "hello.txt", 380 }, 381 wantErr: assert.NoError, 382 }, 383 { 384 name: "pattern", 385 pattern: "*/b/c/*.txt", 386 want: []string{ 387 "a/b/c/c.txt", 388 "a/b/c/virtual.txt", 389 }, 390 wantErr: assert.NoError, 391 }, 392 { 393 name: "no such", 394 pattern: "nosuch", 395 wantErr: assert.NoError, 396 }, 397 } 398 399 for _, tt := range tests { 400 fsys := initFS(t) 401 t.Run(tt.name, func(t *testing.T) { 402 results, err := fsys.Glob(tt.pattern) 403 tt.wantErr(t, err) 404 assert.Equal(t, tt.want, results) 405 }) 406 } 407 } 408 409 func TestFS_Remove(t *testing.T) { 410 tests := []struct { 411 name string 412 path string 413 wantErr assert.ErrorAssertionFunc 414 }{ 415 { 416 name: "ordinary file", 417 path: "hello.txt", 418 wantErr: assert.NoError, 419 }, 420 { 421 name: "nested file", 422 path: "a/b/b.txt", 423 wantErr: assert.NoError, 424 }, 425 { 426 name: "virtual file", 427 path: "a/b/c/virtual.txt", 428 wantErr: assert.NoError, 429 }, 430 { 431 name: "empty dir", 432 path: "a/b/empty", 433 wantErr: assert.NoError, 434 }, 435 { 436 name: "empty path", 437 path: "", 438 wantErr: assert.NoError, 439 }, 440 { 441 name: "non-empty dir", 442 path: "a/b/c", 443 wantErr: assert.Error, 444 }, 445 } 446 447 for _, tt := range tests { 448 fsys := initFS(t) 449 t.Run(tt.name, func(t *testing.T) { 450 err := fsys.Remove(tt.path) 451 tt.wantErr(t, err) 452 if err != nil || tt.path == "" { 453 return 454 } 455 456 _, err = fsys.Stat(tt.path) 457 require.ErrorIs(t, err, fs.ErrNotExist) 458 }) 459 } 460 } 461 462 func TestFS_RemoveAll(t *testing.T) { 463 fsys := initFS(t) 464 t.Run("ordinary file", func(t *testing.T) { 465 err := fsys.RemoveAll("hello.txt") 466 require.NoError(t, err) 467 _, err = fsys.Stat("hello.txt") 468 require.ErrorIs(t, err, fs.ErrNotExist) 469 }) 470 t.Run("non-empty dir", func(t *testing.T) { 471 err := fsys.RemoveAll("a/b") 472 require.NoError(t, err) 473 _, err = fsys.Stat("a/b/c/c.txt") 474 require.ErrorIs(t, err, fs.ErrNotExist) 475 _, err = fsys.Stat("a/b/c/.dotfile") 476 require.ErrorIs(t, err, fs.ErrNotExist) 477 _, err = fsys.Stat("a/b/c/virtual.txt") 478 require.ErrorIs(t, err, fs.ErrNotExist) 479 }) 480 }