github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/sysfs/dir_test.go (about)

     1  package sysfs_test
     2  
     3  import (
     4  	"io"
     5  	"io/fs"
     6  	"os"
     7  	"runtime"
     8  	"sort"
     9  	"testing"
    10  
    11  	"github.com/wasilibs/wazerox/experimental/sys"
    12  	"github.com/wasilibs/wazerox/internal/fstest"
    13  	"github.com/wasilibs/wazerox/internal/sysfs"
    14  	"github.com/wasilibs/wazerox/internal/testing/require"
    15  )
    16  
    17  func TestFSFileReaddir(t *testing.T) {
    18  	t.Parallel()
    19  
    20  	tmpDir := t.TempDir()
    21  	require.NoError(t, fstest.WriteTestFiles(tmpDir))
    22  	dirFS := os.DirFS(tmpDir)
    23  	maskFS := &sysfs.MaskOsFS{Fs: dirFS}
    24  	maskFSZeroIno := &sysfs.MaskOsFS{Fs: os.DirFS(tmpDir), ZeroIno: true}
    25  
    26  	expectIno := runtime.GOOS != "windows"
    27  
    28  	tests := []struct {
    29  		name      string
    30  		fs        fs.FS
    31  		expectIno bool
    32  	}{
    33  		{name: "os.DirFS", fs: dirFS, expectIno: expectIno},                   // To test readdirFile
    34  		{name: "mask(os.DirFS)", fs: maskFS, expectIno: expectIno},            // To prove no reliance on os.File
    35  		{name: "mask(os.DirFS) ZeroIno", fs: maskFSZeroIno, expectIno: false}, // To prove Stat_t overrides
    36  		{name: "fstest.MapFS", fs: fstest.FS, expectIno: false},               // To test adaptation of ReadDirFile
    37  	}
    38  
    39  	for _, tc := range tests {
    40  		tc := tc
    41  
    42  		t.Run(tc.name, func(t *testing.T) {
    43  			dotF, errno := sysfs.OpenFSFile(tc.fs, ".", sys.O_RDONLY, 0)
    44  			require.EqualErrno(t, 0, errno)
    45  			defer dotF.Close()
    46  
    47  			t.Run("dir", func(t *testing.T) {
    48  				testReaddirAll(t, dotF, tc.expectIno)
    49  
    50  				// read again even though it is exhausted
    51  				dirents, errno := dotF.Readdir(100)
    52  				require.EqualErrno(t, 0, errno)
    53  				require.Zero(t, len(dirents))
    54  
    55  				// rewind via seek to zero
    56  				newOffset, errno := dotF.Seek(0, io.SeekStart)
    57  				require.EqualErrno(t, 0, errno)
    58  				require.Zero(t, newOffset)
    59  
    60  				// redundantly seek to zero again
    61  				newOffset, errno = dotF.Seek(0, io.SeekStart)
    62  				require.EqualErrno(t, 0, errno)
    63  				require.Zero(t, newOffset)
    64  
    65  				// We should be able to read again
    66  				testReaddirAll(t, dotF, tc.expectIno)
    67  			})
    68  
    69  			// Err if the caller closed the directory while reading. This is
    70  			// different from something else deleting it.
    71  			t.Run("closed dir", func(t *testing.T) {
    72  				require.EqualErrno(t, 0, dotF.Close())
    73  				_, errno := dotF.Readdir(-1)
    74  				require.EqualErrno(t, sys.EBADF, errno)
    75  			})
    76  
    77  			fileF, errno := sysfs.OpenFSFile(tc.fs, "empty.txt", sys.O_RDONLY, 0)
    78  			require.EqualErrno(t, 0, errno)
    79  			defer fileF.Close()
    80  
    81  			t.Run("file", func(t *testing.T) {
    82  				_, errno := fileF.Readdir(-1)
    83  				require.EqualErrno(t, sys.EBADF, errno)
    84  			})
    85  
    86  			dirF, errno := sysfs.OpenFSFile(tc.fs, "dir", sys.O_RDONLY, 0)
    87  			require.EqualErrno(t, 0, errno)
    88  			defer dirF.Close()
    89  
    90  			t.Run("partial", func(t *testing.T) {
    91  				dirents1, errno := dirF.Readdir(1)
    92  				require.EqualErrno(t, 0, errno)
    93  				require.Equal(t, 1, len(dirents1))
    94  
    95  				dirents2, errno := dirF.Readdir(1)
    96  				require.EqualErrno(t, 0, errno)
    97  				require.Equal(t, 1, len(dirents2))
    98  
    99  				// read exactly the last entry
   100  				dirents3, errno := dirF.Readdir(1)
   101  				require.EqualErrno(t, 0, errno)
   102  				require.Equal(t, 1, len(dirents3))
   103  
   104  				dirents := []sys.Dirent{dirents1[0], dirents2[0], dirents3[0]}
   105  				sort.Slice(dirents, func(i, j int) bool { return dirents[i].Name < dirents[j].Name })
   106  
   107  				requireIno(t, dirents, tc.expectIno)
   108  
   109  				// Scrub inodes so we can compare expectations without them.
   110  				for i := range dirents {
   111  					dirents[i].Ino = 0
   112  				}
   113  
   114  				require.Equal(t, []sys.Dirent{
   115  					{Name: "-", Type: 0},
   116  					{Name: "a-", Type: fs.ModeDir},
   117  					{Name: "ab-", Type: 0},
   118  				}, dirents)
   119  
   120  				// no error reading an exhausted directory
   121  				_, errno = dirF.Readdir(1)
   122  				require.EqualErrno(t, 0, errno)
   123  			})
   124  
   125  			subdirF, errno := sysfs.OpenFSFile(tc.fs, "sub", sys.O_RDONLY, 0)
   126  			require.EqualErrno(t, 0, errno)
   127  			defer subdirF.Close()
   128  
   129  			t.Run("subdir", func(t *testing.T) {
   130  				dirents, errno := subdirF.Readdir(-1)
   131  				require.EqualErrno(t, 0, errno)
   132  				sort.Slice(dirents, func(i, j int) bool { return dirents[i].Name < dirents[j].Name })
   133  
   134  				require.Equal(t, 1, len(dirents))
   135  				require.Equal(t, "test.txt", dirents[0].Name)
   136  				require.Zero(t, dirents[0].Type)
   137  			})
   138  		})
   139  	}
   140  }
   141  
   142  func testReaddirAll(t *testing.T, dotF sys.File, expectDirIno bool) {
   143  	dirents, errno := dotF.Readdir(-1)
   144  	require.EqualErrno(t, 0, errno) // no io.EOF when -1 is used
   145  	sort.Slice(dirents, func(i, j int) bool { return dirents[i].Name < dirents[j].Name })
   146  
   147  	requireIno(t, dirents, expectDirIno)
   148  
   149  	// Scrub inodes so we can compare expectations without them.
   150  	for i := range dirents {
   151  		dirents[i].Ino = 0
   152  	}
   153  
   154  	require.Equal(t, []sys.Dirent{
   155  		{Name: "animals.txt", Type: 0},
   156  		{Name: "dir", Type: fs.ModeDir},
   157  		{Name: "empty.txt", Type: 0},
   158  		{Name: "emptydir", Type: fs.ModeDir},
   159  		{Name: "sub", Type: fs.ModeDir},
   160  	}, dirents)
   161  }
   162  
   163  func requireIno(t *testing.T, dirents []sys.Dirent, expectDirIno bool) {
   164  	for _, e := range dirents {
   165  		if expectDirIno {
   166  			require.NotEqual(t, uint64(0), e.Ino, "%+v", e)
   167  			e.Ino = 0
   168  		} else {
   169  			require.Zero(t, e.Ino, "%+v", e)
   170  		}
   171  	}
   172  }