github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/imports/wasi_snapshot_preview1/fs_unit_test.go (about)

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