github.com/tetratelabs/wazero@v1.2.1/internal/sysfs/dir_test.go (about)

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