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  }