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