github.com/MRtecno98/afero@v1.9.3/iofs_test.go (about) 1 //go:build go1.16 2 // +build go1.16 3 4 package afero 5 6 import ( 7 "bytes" 8 "errors" 9 "fmt" 10 "io" 11 "io/fs" 12 "math/rand" 13 "os" 14 "path/filepath" 15 "runtime" 16 "testing" 17 "testing/fstest" 18 "time" 19 20 "github.com/MRtecno98/afero/internal/common" 21 ) 22 23 func TestIOFS(t *testing.T) { 24 if runtime.GOOS == "windows" { 25 // TODO(bep): some of the "bad path" tests in fstest.TestFS fail on Windows 26 t.Skip("Skipping on Windows") 27 } 28 t.Parallel() 29 30 t.Run("use MemMapFs", func(t *testing.T) { 31 mmfs := NewMemMapFs() 32 33 err := mmfs.MkdirAll("dir1/dir2", os.ModePerm) 34 if err != nil { 35 t.Fatal("MkdirAll failed:", err) 36 } 37 38 f, err := mmfs.OpenFile("dir1/dir2/test.txt", os.O_RDWR|os.O_CREATE, os.ModePerm) 39 if err != nil { 40 t.Fatal("OpenFile (O_CREATE) failed:", err) 41 } 42 43 f.Close() 44 45 if err := fstest.TestFS(NewIOFS(mmfs), "dir1/dir2/test.txt"); err != nil { 46 t.Error(err) 47 } 48 }) 49 50 t.Run("use OsFs", func(t *testing.T) { 51 osfs := NewBasePathFs(NewOsFs(), t.TempDir()) 52 53 err := osfs.MkdirAll("dir1/dir2", os.ModePerm) 54 if err != nil { 55 t.Fatal("MkdirAll failed:", err) 56 } 57 58 f, err := osfs.OpenFile("dir1/dir2/test.txt", os.O_RDWR|os.O_CREATE, os.ModePerm) 59 if err != nil { 60 t.Fatal("OpenFile (O_CREATE) failed:", err) 61 } 62 63 f.Close() 64 65 if err := fstest.TestFS(NewIOFS(osfs), "dir1/dir2/test.txt"); err != nil { 66 t.Error(err) 67 } 68 }) 69 70 } 71 72 func TestIOFSNativeDirEntryWhenPossible(t *testing.T) { 73 t.Parallel() 74 75 osfs := NewBasePathFs(NewOsFs(), t.TempDir()) 76 77 err := osfs.MkdirAll("dir1/dir2", os.ModePerm) 78 if err != nil { 79 t.Fatal(err) 80 } 81 82 const numFiles = 10 83 84 var fileNumbers []int 85 for i := 0; i < numFiles; i++ { 86 fileNumbers = append(fileNumbers, i) 87 } 88 rand.Shuffle(len(fileNumbers), func(i, j int) { 89 fileNumbers[i], fileNumbers[j] = fileNumbers[j], fileNumbers[i] 90 }) 91 92 for _, i := range fileNumbers { 93 f, err := osfs.Create(fmt.Sprintf("dir1/dir2/test%d.txt", i)) 94 if err != nil { 95 t.Fatal(err) 96 } 97 f.Close() 98 } 99 100 dir2, err := osfs.Open("dir1/dir2") 101 if err != nil { 102 t.Fatal(err) 103 } 104 105 assertDirEntries := func(entries []fs.DirEntry, ordered bool) { 106 if len(entries) != numFiles { 107 t.Fatalf("expected %d, got %d", numFiles, len(entries)) 108 } 109 for i, entry := range entries { 110 if _, ok := entry.(common.FileInfoDirEntry); ok { 111 t.Fatal("DirEntry not native") 112 } 113 if ordered && entry.Name() != fmt.Sprintf("test%d.txt", i) { 114 t.Fatalf("expected %s, got %s", fmt.Sprintf("test%d.txt", i), entry.Name()) 115 } 116 } 117 } 118 119 dirEntries, err := dir2.(fs.ReadDirFile).ReadDir(-1) 120 if err != nil { 121 t.Fatal(err) 122 } 123 assertDirEntries(dirEntries, false) 124 125 iofs := NewIOFS(osfs) 126 127 dirEntries, err = iofs.ReadDir("dir1/dir2") 128 if err != nil { 129 t.Fatal(err) 130 } 131 assertDirEntries(dirEntries, true) 132 133 fileCount := 0 134 err = fs.WalkDir(iofs, "", func(path string, d fs.DirEntry, err error) error { 135 if err != nil { 136 return err 137 } 138 139 if !d.IsDir() { 140 fileCount++ 141 } 142 143 if _, ok := d.(common.FileInfoDirEntry); ok { 144 t.Fatal("DirEntry not native") 145 } 146 147 return nil 148 149 }) 150 151 if err != nil { 152 t.Fatal(err) 153 } 154 155 if fileCount != numFiles { 156 t.Fatalf("expected %d, got %d", numFiles, fileCount) 157 } 158 159 } 160 161 func TestFromIOFS(t *testing.T) { 162 t.Parallel() 163 164 fsys := fstest.MapFS{ 165 "test.txt": { 166 Data: []byte("File in root"), 167 Mode: fs.ModePerm, 168 ModTime: time.Now(), 169 }, 170 "dir1": { 171 Mode: fs.ModeDir | fs.ModePerm, 172 ModTime: time.Now(), 173 }, 174 "dir1/dir2": { 175 Mode: fs.ModeDir | fs.ModePerm, 176 ModTime: time.Now(), 177 }, 178 "dir1/dir2/hello.txt": { 179 Data: []byte("Hello world"), 180 Mode: fs.ModePerm, 181 ModTime: time.Now(), 182 }, 183 } 184 185 fromIOFS := FromIOFS{fsys} 186 187 t.Run("Create", func(t *testing.T) { 188 _, err := fromIOFS.Create("test") 189 assertPermissionError(t, err) 190 }) 191 192 t.Run("Mkdir", func(t *testing.T) { 193 err := fromIOFS.Mkdir("test", 0) 194 assertPermissionError(t, err) 195 }) 196 197 t.Run("MkdirAll", func(t *testing.T) { 198 err := fromIOFS.Mkdir("test", 0) 199 assertPermissionError(t, err) 200 }) 201 202 t.Run("Open", func(t *testing.T) { 203 t.Run("non existing file", func(t *testing.T) { 204 _, err := fromIOFS.Open("nonexisting") 205 if !errors.Is(err, fs.ErrNotExist) { 206 t.Errorf("Expected error to be fs.ErrNotExist, got %[1]T (%[1]v)", err) 207 } 208 }) 209 210 t.Run("directory", func(t *testing.T) { 211 dirFile, err := fromIOFS.Open("dir1") 212 if err != nil { 213 t.Errorf("dir1 open failed: %v", err) 214 return 215 } 216 217 defer dirFile.Close() 218 219 dirStat, err := dirFile.Stat() 220 if err != nil { 221 t.Errorf("dir1 stat failed: %v", err) 222 return 223 } 224 225 if !dirStat.IsDir() { 226 t.Errorf("dir1 stat told that it is not a directory") 227 return 228 } 229 }) 230 231 t.Run("simple file", func(t *testing.T) { 232 file, err := fromIOFS.Open("test.txt") 233 if err != nil { 234 t.Errorf("test.txt open failed: %v", err) 235 return 236 } 237 238 defer file.Close() 239 240 fileStat, err := file.Stat() 241 if err != nil { 242 t.Errorf("test.txt stat failed: %v", err) 243 return 244 } 245 246 if fileStat.IsDir() { 247 t.Errorf("test.txt stat told that it is a directory") 248 return 249 } 250 }) 251 }) 252 253 t.Run("Remove", func(t *testing.T) { 254 err := fromIOFS.Remove("test") 255 assertPermissionError(t, err) 256 }) 257 258 t.Run("Rename", func(t *testing.T) { 259 err := fromIOFS.Rename("test", "test2") 260 assertPermissionError(t, err) 261 }) 262 263 t.Run("Stat", func(t *testing.T) { 264 t.Run("non existing file", func(t *testing.T) { 265 _, err := fromIOFS.Stat("nonexisting") 266 if !errors.Is(err, fs.ErrNotExist) { 267 t.Errorf("Expected error to be fs.ErrNotExist, got %[1]T (%[1]v)", err) 268 } 269 }) 270 271 t.Run("directory", func(t *testing.T) { 272 stat, err := fromIOFS.Stat("dir1/dir2") 273 if err != nil { 274 t.Errorf("dir1/dir2 stat failed: %v", err) 275 return 276 } 277 278 if !stat.IsDir() { 279 t.Errorf("dir1/dir2 stat told that it is not a directory") 280 return 281 } 282 }) 283 284 t.Run("file", func(t *testing.T) { 285 stat, err := fromIOFS.Stat("dir1/dir2/hello.txt") 286 if err != nil { 287 t.Errorf("dir1/dir2 stat failed: %v", err) 288 return 289 } 290 291 if stat.IsDir() { 292 t.Errorf("dir1/dir2/hello.txt stat told that it is a directory") 293 return 294 } 295 296 if lenFile := len(fsys["dir1/dir2/hello.txt"].Data); int64(lenFile) != stat.Size() { 297 t.Errorf("dir1/dir2/hello.txt stat told invalid size: expected %d, got %d", lenFile, stat.Size()) 298 return 299 } 300 }) 301 }) 302 303 t.Run("Chmod", func(t *testing.T) { 304 err := fromIOFS.Chmod("test", os.ModePerm) 305 assertPermissionError(t, err) 306 }) 307 308 t.Run("Chown", func(t *testing.T) { 309 err := fromIOFS.Chown("test", 0, 0) 310 assertPermissionError(t, err) 311 }) 312 313 t.Run("Chtimes", func(t *testing.T) { 314 err := fromIOFS.Chtimes("test", time.Now(), time.Now()) 315 assertPermissionError(t, err) 316 }) 317 } 318 319 func TestFromIOFS_File(t *testing.T) { 320 t.Parallel() 321 322 fsys := fstest.MapFS{ 323 "test.txt": { 324 Data: []byte("File in root"), 325 Mode: fs.ModePerm, 326 ModTime: time.Now(), 327 }, 328 "dir1": { 329 Mode: fs.ModeDir | fs.ModePerm, 330 ModTime: time.Now(), 331 }, 332 "dir2": { 333 Mode: fs.ModeDir | fs.ModePerm, 334 ModTime: time.Now(), 335 }, 336 } 337 338 fromIOFS := FromIOFS{fsys} 339 340 file, err := fromIOFS.Open("test.txt") 341 if err != nil { 342 t.Errorf("test.txt open failed: %v", err) 343 return 344 } 345 346 defer file.Close() 347 348 fileStat, err := file.Stat() 349 if err != nil { 350 t.Errorf("test.txt stat failed: %v", err) 351 return 352 } 353 354 if fileStat.IsDir() { 355 t.Errorf("test.txt stat told that it is a directory") 356 return 357 } 358 359 t.Run("ReadAt", func(t *testing.T) { 360 // MapFS files implements io.ReaderAt 361 b := make([]byte, 2) 362 _, err := file.ReadAt(b, 2) 363 364 if err != nil { 365 t.Errorf("ReadAt failed: %v", err) 366 return 367 } 368 369 if expectedData := fsys["test.txt"].Data[2:4]; !bytes.Equal(b, expectedData) { 370 t.Errorf("Unexpected content read: %s, expected %s", b, expectedData) 371 } 372 }) 373 374 t.Run("Seek", func(t *testing.T) { 375 n, err := file.Seek(2, io.SeekStart) 376 if err != nil { 377 t.Errorf("Seek failed: %v", err) 378 return 379 } 380 381 if n != 2 { 382 t.Errorf("Seek returned unexpected value: %d, expected 2", n) 383 } 384 }) 385 386 t.Run("Write", func(t *testing.T) { 387 _, err := file.Write(nil) 388 assertPermissionError(t, err) 389 }) 390 391 t.Run("WriteAt", func(t *testing.T) { 392 _, err := file.WriteAt(nil, 0) 393 assertPermissionError(t, err) 394 }) 395 396 t.Run("Name", func(t *testing.T) { 397 if name := file.Name(); name != "test.txt" { 398 t.Errorf("expected file.Name() == test.txt, got %s", name) 399 } 400 }) 401 402 t.Run("Readdir", func(t *testing.T) { 403 t.Run("not directory", func(t *testing.T) { 404 _, err := file.Readdir(-1) 405 assertPermissionError(t, err) 406 }) 407 408 t.Run("root directory", func(t *testing.T) { 409 root, err := fromIOFS.Open(".") 410 if err != nil { 411 t.Errorf("root open failed: %v", err) 412 return 413 } 414 415 defer root.Close() 416 417 items, err := root.Readdir(-1) 418 if err != nil { 419 t.Errorf("Readdir error: %v", err) 420 return 421 } 422 423 var expectedItems = []struct { 424 Name string 425 IsDir bool 426 Size int64 427 }{ 428 {Name: "dir1", IsDir: true, Size: 0}, 429 {Name: "dir2", IsDir: true, Size: 0}, 430 {Name: "test.txt", IsDir: false, Size: int64(len(fsys["test.txt"].Data))}, 431 } 432 433 if len(expectedItems) != len(items) { 434 t.Errorf("Items count mismatch, expected %d, got %d", len(expectedItems), len(items)) 435 return 436 } 437 438 for i, item := range items { 439 if item.Name() != expectedItems[i].Name { 440 t.Errorf("Item %d: expected name %s, got %s", i, expectedItems[i].Name, item.Name()) 441 } 442 443 if item.IsDir() != expectedItems[i].IsDir { 444 t.Errorf("Item %d: expected IsDir %t, got %t", i, expectedItems[i].IsDir, item.IsDir()) 445 } 446 447 if item.Size() != expectedItems[i].Size { 448 t.Errorf("Item %d: expected IsDir %d, got %d", i, expectedItems[i].Size, item.Size()) 449 } 450 } 451 }) 452 }) 453 454 t.Run("Readdirnames", func(t *testing.T) { 455 t.Run("not directory", func(t *testing.T) { 456 _, err := file.Readdirnames(-1) 457 assertPermissionError(t, err) 458 }) 459 460 t.Run("root directory", func(t *testing.T) { 461 root, err := fromIOFS.Open(".") 462 if err != nil { 463 t.Errorf("root open failed: %v", err) 464 return 465 } 466 467 defer root.Close() 468 469 items, err := root.Readdirnames(-1) 470 if err != nil { 471 t.Errorf("Readdirnames error: %v", err) 472 return 473 } 474 475 var expectedItems = []string{"dir1", "dir2", "test.txt"} 476 477 if len(expectedItems) != len(items) { 478 t.Errorf("Items count mismatch, expected %d, got %d", len(expectedItems), len(items)) 479 return 480 } 481 482 for i, item := range items { 483 if item != expectedItems[i] { 484 t.Errorf("Item %d: expected name %s, got %s", i, expectedItems[i], item) 485 } 486 } 487 }) 488 }) 489 490 t.Run("Truncate", func(t *testing.T) { 491 err := file.Truncate(1) 492 assertPermissionError(t, err) 493 }) 494 495 t.Run("WriteString", func(t *testing.T) { 496 _, err := file.WriteString("a") 497 assertPermissionError(t, err) 498 }) 499 } 500 501 func assertPermissionError(t *testing.T, err error) { 502 t.Helper() 503 504 var perr *fs.PathError 505 if !errors.As(err, &perr) { 506 t.Errorf("Expected *fs.PathError, got %[1]T (%[1]v)", err) 507 return 508 } 509 510 if perr.Err != fs.ErrPermission { 511 t.Errorf("Expected (*fs.PathError).Err == fs.ErrPermisson, got %[1]T (%[1]v)", err) 512 } 513 } 514 515 func BenchmarkWalkDir(b *testing.B) { 516 osfs := NewBasePathFs(NewOsFs(), b.TempDir()) 517 518 createSomeFiles := func(dirname string) { 519 for i := 0; i < 10; i++ { 520 f, err := osfs.Create(filepath.Join(dirname, fmt.Sprintf("test%d.txt", i))) 521 if err != nil { 522 b.Fatal(err) 523 } 524 f.Close() 525 } 526 } 527 528 depth := 10 529 for level := depth; level > 0; level-- { 530 dirname := "" 531 for i := 0; i < level; i++ { 532 dirname = filepath.Join(dirname, fmt.Sprintf("dir%d", i)) 533 err := osfs.MkdirAll(dirname, 0755) 534 if err != nil && !os.IsExist(err) { 535 b.Fatal(err) 536 } 537 } 538 createSomeFiles(dirname) 539 } 540 541 iofs := NewIOFS(osfs) 542 543 b.ResetTimer() 544 for i := 0; i < b.N; i++ { 545 err := fs.WalkDir(iofs, "", func(path string, d fs.DirEntry, err error) error { 546 if err != nil { 547 return err 548 } 549 return nil 550 551 }) 552 553 if err != nil { 554 b.Fatal(err) 555 } 556 } 557 558 }