github.com/gozelle/afero@v0.0.0-20230510083704-09e2ff18f19e/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/gozelle/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  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  		// MapFS files implements io.ReaderAt
   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  }