1
2
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/spf13/afero/internal/common"
21 )
22
23 func TestIOFS(t *testing.T) {
24 if runtime.GOOS == "windows" {
25
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 func TestIOFSNativeDirEntryWhenPossible(t *testing.T) {
72 t.Parallel()
73
74 osfs := NewBasePathFs(NewOsFs(), t.TempDir())
75
76 err := osfs.MkdirAll("dir1/dir2", os.ModePerm)
77 if err != nil {
78 t.Fatal(err)
79 }
80
81 const numFiles = 10
82
83 var fileNumbers []int
84 for i := 0; i < numFiles; i++ {
85 fileNumbers = append(fileNumbers, i)
86 }
87 rand.Shuffle(len(fileNumbers), func(i, j int) {
88 fileNumbers[i], fileNumbers[j] = fileNumbers[j], fileNumbers[i]
89 })
90
91 for _, i := range fileNumbers {
92 f, err := osfs.Create(fmt.Sprintf("dir1/dir2/test%d.txt", i))
93 if err != nil {
94 t.Fatal(err)
95 }
96 f.Close()
97 }
98
99 dir2, err := osfs.Open("dir1/dir2")
100 if err != nil {
101 t.Fatal(err)
102 }
103 defer dir2.Close()
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 if err != nil {
151 t.Fatal(err)
152 }
153
154 if fileCount != numFiles {
155 t.Fatalf("expected %d, got %d", numFiles, fileCount)
156 }
157 }
158
159 func TestFromIOFS(t *testing.T) {
160 t.Parallel()
161
162 fsys := fstest.MapFS{
163 "test.txt": {
164 Data: []byte("File in root"),
165 Mode: fs.ModePerm,
166 ModTime: time.Now(),
167 },
168 "dir1": {
169 Mode: fs.ModeDir | fs.ModePerm,
170 ModTime: time.Now(),
171 },
172 "dir1/dir2": {
173 Mode: fs.ModeDir | fs.ModePerm,
174 ModTime: time.Now(),
175 },
176 "dir1/dir2/hello.txt": {
177 Data: []byte("Hello world"),
178 Mode: fs.ModePerm,
179 ModTime: time.Now(),
180 },
181 }
182
183 fromIOFS := FromIOFS{fsys}
184
185 t.Run("Create", func(t *testing.T) {
186 _, err := fromIOFS.Create("test")
187 assertPermissionError(t, err)
188 })
189
190 t.Run("Mkdir", func(t *testing.T) {
191 err := fromIOFS.Mkdir("test", 0)
192 assertPermissionError(t, err)
193 })
194
195 t.Run("MkdirAll", func(t *testing.T) {
196 err := fromIOFS.Mkdir("test", 0)
197 assertPermissionError(t, err)
198 })
199
200 t.Run("Open", func(t *testing.T) {
201 t.Run("non existing file", func(t *testing.T) {
202 _, err := fromIOFS.Open("nonexisting")
203 if !errors.Is(err, fs.ErrNotExist) {
204 t.Errorf("Expected error to be fs.ErrNotExist, got %[1]T (%[1]v)", err)
205 }
206 })
207
208 t.Run("directory", func(t *testing.T) {
209 dirFile, err := fromIOFS.Open("dir1")
210 if err != nil {
211 t.Errorf("dir1 open failed: %v", err)
212 return
213 }
214
215 defer dirFile.Close()
216
217 dirStat, err := dirFile.Stat()
218 if err != nil {
219 t.Errorf("dir1 stat failed: %v", err)
220 return
221 }
222
223 if !dirStat.IsDir() {
224 t.Errorf("dir1 stat told that it is not a directory")
225 return
226 }
227 })
228
229 t.Run("simple file", func(t *testing.T) {
230 file, err := fromIOFS.Open("test.txt")
231 if err != nil {
232 t.Errorf("test.txt open failed: %v", err)
233 return
234 }
235
236 defer file.Close()
237
238 fileStat, err := file.Stat()
239 if err != nil {
240 t.Errorf("test.txt stat failed: %v", err)
241 return
242 }
243
244 if fileStat.IsDir() {
245 t.Errorf("test.txt stat told that it is a directory")
246 return
247 }
248 })
249 })
250
251 t.Run("Remove", func(t *testing.T) {
252 err := fromIOFS.Remove("test")
253 assertPermissionError(t, err)
254 })
255
256 t.Run("Rename", func(t *testing.T) {
257 err := fromIOFS.Rename("test", "test2")
258 assertPermissionError(t, err)
259 })
260
261 t.Run("Stat", func(t *testing.T) {
262 t.Run("non existing file", func(t *testing.T) {
263 _, err := fromIOFS.Stat("nonexisting")
264 if !errors.Is(err, fs.ErrNotExist) {
265 t.Errorf("Expected error to be fs.ErrNotExist, got %[1]T (%[1]v)", err)
266 }
267 })
268
269 t.Run("directory", func(t *testing.T) {
270 stat, err := fromIOFS.Stat("dir1/dir2")
271 if err != nil {
272 t.Errorf("dir1/dir2 stat failed: %v", err)
273 return
274 }
275
276 if !stat.IsDir() {
277 t.Errorf("dir1/dir2 stat told that it is not a directory")
278 return
279 }
280 })
281
282 t.Run("file", func(t *testing.T) {
283 stat, err := fromIOFS.Stat("dir1/dir2/hello.txt")
284 if err != nil {
285 t.Errorf("dir1/dir2 stat failed: %v", err)
286 return
287 }
288
289 if stat.IsDir() {
290 t.Errorf("dir1/dir2/hello.txt stat told that it is a directory")
291 return
292 }
293
294 if lenFile := len(fsys["dir1/dir2/hello.txt"].Data); int64(lenFile) != stat.Size() {
295 t.Errorf("dir1/dir2/hello.txt stat told invalid size: expected %d, got %d", lenFile, stat.Size())
296 return
297 }
298 })
299 })
300
301 t.Run("Chmod", func(t *testing.T) {
302 err := fromIOFS.Chmod("test", os.ModePerm)
303 assertPermissionError(t, err)
304 })
305
306 t.Run("Chown", func(t *testing.T) {
307 err := fromIOFS.Chown("test", 0, 0)
308 assertPermissionError(t, err)
309 })
310
311 t.Run("Chtimes", func(t *testing.T) {
312 err := fromIOFS.Chtimes("test", time.Now(), time.Now())
313 assertPermissionError(t, err)
314 })
315 }
316
317 func TestFromIOFS_File(t *testing.T) {
318 t.Parallel()
319
320 fsys := fstest.MapFS{
321 "test.txt": {
322 Data: []byte("File in root"),
323 Mode: fs.ModePerm,
324 ModTime: time.Now(),
325 },
326 "dir1": {
327 Mode: fs.ModeDir | fs.ModePerm,
328 ModTime: time.Now(),
329 },
330 "dir2": {
331 Mode: fs.ModeDir | fs.ModePerm,
332 ModTime: time.Now(),
333 },
334 }
335
336 fromIOFS := FromIOFS{fsys}
337
338 file, err := fromIOFS.Open("test.txt")
339 if err != nil {
340 t.Errorf("test.txt open failed: %v", err)
341 return
342 }
343
344 defer file.Close()
345
346 fileStat, err := file.Stat()
347 if err != nil {
348 t.Errorf("test.txt stat failed: %v", err)
349 return
350 }
351
352 if fileStat.IsDir() {
353 t.Errorf("test.txt stat told that it is a directory")
354 return
355 }
356
357 t.Run("ReadAt", func(t *testing.T) {
358
359 b := make([]byte, 2)
360 _, err := file.ReadAt(b, 2)
361 if err != nil {
362 t.Errorf("ReadAt failed: %v", err)
363 return
364 }
365
366 if expectedData := fsys["test.txt"].Data[2:4]; !bytes.Equal(b, expectedData) {
367 t.Errorf("Unexpected content read: %s, expected %s", b, expectedData)
368 }
369 })
370
371 t.Run("Seek", func(t *testing.T) {
372 n, err := file.Seek(2, io.SeekStart)
373 if err != nil {
374 t.Errorf("Seek failed: %v", err)
375 return
376 }
377
378 if n != 2 {
379 t.Errorf("Seek returned unexpected value: %d, expected 2", n)
380 }
381 })
382
383 t.Run("Write", func(t *testing.T) {
384 _, err := file.Write(nil)
385 assertPermissionError(t, err)
386 })
387
388 t.Run("WriteAt", func(t *testing.T) {
389 _, err := file.WriteAt(nil, 0)
390 assertPermissionError(t, err)
391 })
392
393 t.Run("Name", func(t *testing.T) {
394 if name := file.Name(); name != "test.txt" {
395 t.Errorf("expected file.Name() == test.txt, got %s", name)
396 }
397 })
398
399 t.Run("Readdir", func(t *testing.T) {
400 t.Run("not directory", func(t *testing.T) {
401 _, err := file.Readdir(-1)
402 assertPermissionError(t, err)
403 })
404
405 t.Run("root directory", func(t *testing.T) {
406 root, err := fromIOFS.Open(".")
407 if err != nil {
408 t.Errorf("root open failed: %v", err)
409 return
410 }
411
412 defer root.Close()
413
414 items, err := root.Readdir(-1)
415 if err != nil {
416 t.Errorf("Readdir error: %v", err)
417 return
418 }
419
420 expectedItems := []struct {
421 Name string
422 IsDir bool
423 Size int64
424 }{
425 {Name: "dir1", IsDir: true, Size: 0},
426 {Name: "dir2", IsDir: true, Size: 0},
427 {Name: "test.txt", IsDir: false, Size: int64(len(fsys["test.txt"].Data))},
428 }
429
430 if len(expectedItems) != len(items) {
431 t.Errorf("Items count mismatch, expected %d, got %d", len(expectedItems), len(items))
432 return
433 }
434
435 for i, item := range items {
436 if item.Name() != expectedItems[i].Name {
437 t.Errorf("Item %d: expected name %s, got %s", i, expectedItems[i].Name, item.Name())
438 }
439
440 if item.IsDir() != expectedItems[i].IsDir {
441 t.Errorf("Item %d: expected IsDir %t, got %t", i, expectedItems[i].IsDir, item.IsDir())
442 }
443
444 if item.Size() != expectedItems[i].Size {
445 t.Errorf("Item %d: expected IsDir %d, got %d", i, expectedItems[i].Size, item.Size())
446 }
447 }
448 })
449 })
450
451 t.Run("Readdirnames", func(t *testing.T) {
452 t.Run("not directory", func(t *testing.T) {
453 _, err := file.Readdirnames(-1)
454 assertPermissionError(t, err)
455 })
456
457 t.Run("root directory", func(t *testing.T) {
458 root, err := fromIOFS.Open(".")
459 if err != nil {
460 t.Errorf("root open failed: %v", err)
461 return
462 }
463
464 defer root.Close()
465
466 items, err := root.Readdirnames(-1)
467 if err != nil {
468 t.Errorf("Readdirnames error: %v", err)
469 return
470 }
471
472 expectedItems := []string{"dir1", "dir2", "test.txt"}
473
474 if len(expectedItems) != len(items) {
475 t.Errorf("Items count mismatch, expected %d, got %d", len(expectedItems), len(items))
476 return
477 }
478
479 for i, item := range items {
480 if item != expectedItems[i] {
481 t.Errorf("Item %d: expected name %s, got %s", i, expectedItems[i], item)
482 }
483 }
484 })
485 })
486
487 t.Run("Truncate", func(t *testing.T) {
488 err := file.Truncate(1)
489 assertPermissionError(t, err)
490 })
491
492 t.Run("WriteString", func(t *testing.T) {
493 _, err := file.WriteString("a")
494 assertPermissionError(t, err)
495 })
496 }
497
498 func assertPermissionError(t *testing.T, err error) {
499 t.Helper()
500
501 var perr *fs.PathError
502 if !errors.As(err, &perr) {
503 t.Errorf("Expected *fs.PathError, got %[1]T (%[1]v)", err)
504 return
505 }
506
507 if perr.Err != fs.ErrPermission {
508 t.Errorf("Expected (*fs.PathError).Err == fs.ErrPermisson, got %[1]T (%[1]v)", err)
509 }
510 }
511
512 func BenchmarkWalkDir(b *testing.B) {
513 osfs := NewBasePathFs(NewOsFs(), b.TempDir())
514
515 createSomeFiles := func(dirname string) {
516 for i := 0; i < 10; i++ {
517 f, err := osfs.Create(filepath.Join(dirname, fmt.Sprintf("test%d.txt", i)))
518 if err != nil {
519 b.Fatal(err)
520 }
521 f.Close()
522 }
523 }
524
525 depth := 10
526 for level := depth; level > 0; level-- {
527 dirname := ""
528 for i := 0; i < level; i++ {
529 dirname = filepath.Join(dirname, fmt.Sprintf("dir%d", i))
530 err := osfs.MkdirAll(dirname, 0o755)
531 if err != nil && !os.IsExist(err) {
532 b.Fatal(err)
533 }
534 }
535 createSomeFiles(dirname)
536 }
537
538 iofs := NewIOFS(osfs)
539
540 b.ResetTimer()
541 for i := 0; i < b.N; i++ {
542 err := fs.WalkDir(iofs, "", func(path string, d fs.DirEntry, err error) error {
543 if err != nil {
544 return err
545 }
546 return nil
547 })
548 if err != nil {
549 b.Fatal(err)
550 }
551 }
552 }
553
View as plain text