github.com/tetratelabs/wazero@v1.2.1/imports/wasi_snapshot_preview1/fs_unit_test.go (about)

     1  package wasi_snapshot_preview1
     2  
     3  import (
     4  	"os"
     5  	"syscall"
     6  	"testing"
     7  
     8  	"github.com/tetratelabs/wazero/internal/fsapi"
     9  	"github.com/tetratelabs/wazero/internal/fstest"
    10  	"github.com/tetratelabs/wazero/internal/sys"
    11  	"github.com/tetratelabs/wazero/internal/sysfs"
    12  	"github.com/tetratelabs/wazero/internal/testing/require"
    13  	"github.com/tetratelabs/wazero/internal/wasip1"
    14  )
    15  
    16  func Test_maxDirents(t *testing.T) {
    17  	tests := []struct {
    18  		name                        string
    19  		dirents                     []fsapi.Dirent
    20  		maxLen                      uint32
    21  		expectedCount               uint32
    22  		expectedwriteTruncatedEntry bool
    23  		expectedBufused             uint32
    24  	}{
    25  		{
    26  			name: "no entries",
    27  		},
    28  		{
    29  			name:                        "can't fit one",
    30  			dirents:                     testDirents,
    31  			maxLen:                      23,
    32  			expectedBufused:             23,
    33  			expectedwriteTruncatedEntry: false,
    34  		},
    35  		{
    36  			name:                        "only fits header",
    37  			dirents:                     testDirents,
    38  			maxLen:                      24,
    39  			expectedBufused:             24,
    40  			expectedwriteTruncatedEntry: true,
    41  		},
    42  		{
    43  			name:            "one",
    44  			dirents:         testDirents,
    45  			maxLen:          25,
    46  			expectedCount:   1,
    47  			expectedBufused: 25,
    48  		},
    49  		{
    50  			name:                        "one but not room for two's name",
    51  			dirents:                     testDirents,
    52  			maxLen:                      25 + 25,
    53  			expectedCount:               1,
    54  			expectedwriteTruncatedEntry: true, // can write DirentSize
    55  			expectedBufused:             25 + 25,
    56  		},
    57  		{
    58  			name:            "two",
    59  			dirents:         testDirents,
    60  			maxLen:          25 + 26,
    61  			expectedCount:   2,
    62  			expectedBufused: 25 + 26,
    63  		},
    64  		{
    65  			name:                        "two but not three's dirent",
    66  			dirents:                     testDirents,
    67  			maxLen:                      25 + 26 + 20,
    68  			expectedCount:               2,
    69  			expectedwriteTruncatedEntry: false, // 20 + 4 == DirentSize
    70  			expectedBufused:             25 + 26 + 20,
    71  		},
    72  		{
    73  			name:                        "two but not three's name",
    74  			dirents:                     testDirents,
    75  			maxLen:                      25 + 26 + 26,
    76  			expectedCount:               2,
    77  			expectedwriteTruncatedEntry: true, // can write DirentSize
    78  			expectedBufused:             25 + 26 + 26,
    79  		},
    80  		{
    81  			name:                        "three",
    82  			dirents:                     testDirents,
    83  			maxLen:                      25 + 26 + 27,
    84  			expectedCount:               3,
    85  			expectedwriteTruncatedEntry: false, // end of dir
    86  			expectedBufused:             25 + 26 + 27,
    87  		},
    88  		{
    89  			name:                        "max",
    90  			dirents:                     testDirents,
    91  			maxLen:                      100,
    92  			expectedCount:               3,
    93  			expectedwriteTruncatedEntry: false, // end of dir
    94  			expectedBufused:             25 + 26 + 27,
    95  		},
    96  	}
    97  
    98  	for _, tt := range tests {
    99  		tc := tt
   100  
   101  		t.Run(tc.name, func(t *testing.T) {
   102  			readdir, _ := sys.NewReaddir(
   103  				func() ([]fsapi.Dirent, syscall.Errno) {
   104  					return tc.dirents, 0
   105  				},
   106  				func(n uint64) ([]fsapi.Dirent, syscall.Errno) {
   107  					return nil, 0
   108  				},
   109  			)
   110  			_, bufused, direntCount, writeTruncatedEntry := maxDirents(readdir, tc.maxLen)
   111  			require.Equal(t, tc.expectedCount, direntCount)
   112  			require.Equal(t, tc.expectedwriteTruncatedEntry, writeTruncatedEntry)
   113  			require.Equal(t, tc.expectedBufused, bufused)
   114  		})
   115  	}
   116  }
   117  
   118  var (
   119  	testDirents = func() []fsapi.Dirent {
   120  		dPath := "dir"
   121  		d, errno := sysfs.OpenFSFile(fstest.FS, dPath, syscall.O_RDONLY, 0)
   122  		if errno != 0 {
   123  			panic(errno)
   124  		}
   125  		defer d.Close()
   126  		dirents, errno := d.Readdir(-1)
   127  		if errno != 0 {
   128  			panic(errno)
   129  		}
   130  		return dirents
   131  	}()
   132  
   133  	dirent1 = []byte{
   134  		1, 0, 0, 0, 0, 0, 0, 0, // d_next = 1
   135  		0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0
   136  		1, 0, 0, 0, // d_namlen = 1 character
   137  		4, 0, 0, 0, // d_type = regular_file
   138  		'-', // name
   139  	}
   140  	dirent2 = []byte{
   141  		2, 0, 0, 0, 0, 0, 0, 0, // d_next = 2
   142  		0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0
   143  		2, 0, 0, 0, // d_namlen = 1 character
   144  		3, 0, 0, 0, // d_type =  directory
   145  		'a', '-', // name
   146  	}
   147  	dirent3 = []byte{
   148  		3, 0, 0, 0, 0, 0, 0, 0, // d_next = 3
   149  		0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0
   150  		3, 0, 0, 0, // d_namlen = 3 characters
   151  		4, 0, 0, 0, // d_type = regular_file
   152  		'a', 'b', '-', // name
   153  	}
   154  )
   155  
   156  func Test_writeDirents(t *testing.T) {
   157  	tests := []struct {
   158  		name                string
   159  		entries             []fsapi.Dirent
   160  		entryCount          uint32
   161  		writeTruncatedEntry bool
   162  		expectedEntriesBuf  []byte
   163  	}{
   164  		{
   165  			name:    "none",
   166  			entries: testDirents,
   167  		},
   168  		{
   169  			name:               "one",
   170  			entries:            testDirents,
   171  			entryCount:         1,
   172  			expectedEntriesBuf: dirent1,
   173  		},
   174  		{
   175  			name:               "two",
   176  			entries:            testDirents,
   177  			entryCount:         2,
   178  			expectedEntriesBuf: append(dirent1, dirent2...),
   179  		},
   180  		{
   181  			name:                "two with truncated",
   182  			entries:             testDirents,
   183  			entryCount:          2,
   184  			writeTruncatedEntry: true,
   185  			expectedEntriesBuf:  append(append(dirent1, dirent2...), dirent3[0:10]...),
   186  		},
   187  		{
   188  			name:               "three",
   189  			entries:            testDirents,
   190  			entryCount:         3,
   191  			expectedEntriesBuf: append(append(dirent1, dirent2...), dirent3...),
   192  		},
   193  	}
   194  
   195  	for _, tt := range tests {
   196  		tc := tt
   197  
   198  		t.Run(tc.name, func(t *testing.T) {
   199  			cookie := uint64(1)
   200  			entriesBuf := make([]byte, len(tc.expectedEntriesBuf))
   201  			writeDirents(tc.entries, tc.entryCount, tc.writeTruncatedEntry, entriesBuf, cookie)
   202  			require.Equal(t, tc.expectedEntriesBuf, entriesBuf)
   203  		})
   204  	}
   205  }
   206  
   207  func Test_openFlags(t *testing.T) {
   208  	tests := []struct {
   209  		name                      string
   210  		dirflags, oflags, fdflags uint16
   211  		rights                    uint32
   212  		expectedOpenFlags         int
   213  	}{
   214  		{
   215  			name:              "oflags=0",
   216  			expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDONLY,
   217  		},
   218  		{
   219  			name:              "oflags=O_CREAT",
   220  			oflags:            wasip1.O_CREAT,
   221  			expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR | syscall.O_CREAT,
   222  		},
   223  		{
   224  			name:              "oflags=O_DIRECTORY",
   225  			oflags:            wasip1.O_DIRECTORY,
   226  			expectedOpenFlags: fsapi.O_NOFOLLOW | fsapi.O_DIRECTORY,
   227  		},
   228  		{
   229  			name:              "oflags=O_EXCL",
   230  			oflags:            wasip1.O_EXCL,
   231  			expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDONLY | syscall.O_EXCL,
   232  		},
   233  		{
   234  			name:              "oflags=O_TRUNC",
   235  			oflags:            wasip1.O_TRUNC,
   236  			expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR | syscall.O_TRUNC,
   237  		},
   238  		{
   239  			name:              "fdflags=FD_APPEND",
   240  			fdflags:           wasip1.FD_APPEND,
   241  			expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR | syscall.O_APPEND,
   242  		},
   243  		{
   244  			name:              "oflags=O_TRUNC|O_CREAT",
   245  			oflags:            wasip1.O_TRUNC | wasip1.O_CREAT,
   246  			expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR | syscall.O_TRUNC | syscall.O_CREAT,
   247  		},
   248  		{
   249  			name:              "dirflags=LOOKUP_SYMLINK_FOLLOW",
   250  			dirflags:          wasip1.LOOKUP_SYMLINK_FOLLOW,
   251  			expectedOpenFlags: syscall.O_RDONLY,
   252  		},
   253  		{
   254  			name:              "rights=FD_READ",
   255  			rights:            wasip1.RIGHT_FD_READ,
   256  			expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDONLY,
   257  		},
   258  		{
   259  			name:              "rights=FD_WRITE",
   260  			rights:            wasip1.RIGHT_FD_WRITE,
   261  			expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_WRONLY,
   262  		},
   263  		{
   264  			name:              "rights=FD_READ|FD_WRITE",
   265  			rights:            wasip1.RIGHT_FD_READ | wasip1.RIGHT_FD_WRITE,
   266  			expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR,
   267  		},
   268  	}
   269  
   270  	for _, tt := range tests {
   271  		tc := tt
   272  
   273  		t.Run(tc.name, func(t *testing.T) {
   274  			openFlags := openFlags(tc.dirflags, tc.oflags, tc.fdflags, tc.rights)
   275  			require.Equal(t, tc.expectedOpenFlags, openFlags)
   276  		})
   277  	}
   278  }
   279  
   280  func Test_getWasiFiletype_DevNull(t *testing.T) {
   281  	st, err := os.Stat(os.DevNull)
   282  	require.NoError(t, err)
   283  
   284  	ft := getWasiFiletype(st.Mode())
   285  
   286  	// Should be a character device, and not contain permissions
   287  	require.Equal(t, wasip1.FILETYPE_CHARACTER_DEVICE, ft)
   288  }
   289  
   290  func Test_isPreopenedStdio(t *testing.T) {
   291  	tests := []struct {
   292  		name     string
   293  		fd       int32
   294  		f        *sys.FileEntry
   295  		expected bool
   296  	}{
   297  		{
   298  			name:     "stdin",
   299  			fd:       sys.FdStdin,
   300  			f:        &sys.FileEntry{IsPreopen: true},
   301  			expected: true,
   302  		},
   303  		{
   304  			name:     "stdin re-opened",
   305  			fd:       sys.FdStdin,
   306  			f:        &sys.FileEntry{IsPreopen: false},
   307  			expected: false,
   308  		},
   309  		{
   310  			name:     "stdout",
   311  			fd:       sys.FdStdout,
   312  			f:        &sys.FileEntry{IsPreopen: true},
   313  			expected: true,
   314  		},
   315  		{
   316  			name:     "stdout re-opened",
   317  			fd:       sys.FdStdout,
   318  			f:        &sys.FileEntry{IsPreopen: false},
   319  			expected: false,
   320  		},
   321  		{
   322  			name:     "stderr",
   323  			fd:       sys.FdStderr,
   324  			f:        &sys.FileEntry{IsPreopen: true},
   325  			expected: true,
   326  		},
   327  		{
   328  			name:     "stderr re-opened",
   329  			fd:       sys.FdStderr,
   330  			f:        &sys.FileEntry{IsPreopen: false},
   331  			expected: false,
   332  		},
   333  		{
   334  			name:     "not stdio pre-open",
   335  			fd:       sys.FdPreopen,
   336  			f:        &sys.FileEntry{IsPreopen: true},
   337  			expected: false,
   338  		},
   339  		{
   340  			name:     "random file",
   341  			fd:       42,
   342  			f:        &sys.FileEntry{},
   343  			expected: false,
   344  		},
   345  	}
   346  
   347  	for _, tt := range tests {
   348  		tc := tt
   349  
   350  		t.Run(tc.name, func(t *testing.T) {
   351  			ok := isPreopenedStdio(tc.fd, tc.f)
   352  			require.Equal(t, tc.expected, ok)
   353  		})
   354  	}
   355  }