Files
wazero/internal/sys/fs_test.go
Crypt Keeper 319d6cca62 fs: adds base UnimplementedFS type and unwraps PathError (#1046)
This reduces some boilerplate by extracting UnimplementedFS from the
existing FS implementations, such that it returns ENOSYS. This also
removes inconsistency where some methods on FS returned syscall.Errno
and others PathError.

Note: this doesn't get rid of all PathError, yet. We still need to
create a syscallfs.File type which would be able to do that. This is
just one preliminary cleanup before refactoring out the `fs.FS`
embedding from `syscallfs.DS`.

P.S. naming convention is arbitrary, so I took UnimplementedXXX from
grpc. This pattern is used a lot of places, also proxy-wasm-go-sdk, e.g.
`DefaultVMContext`.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2023-01-18 09:37:12 -06:00

156 lines
4.2 KiB
Go

package sys
import (
"context"
"embed"
"errors"
"io"
"io/fs"
"os"
"testing"
"testing/fstest"
"github.com/tetratelabs/wazero/internal/syscallfs"
testfs "github.com/tetratelabs/wazero/internal/testing/fs"
"github.com/tetratelabs/wazero/internal/testing/require"
)
// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
var (
noopStdin = &FileEntry{File: &stdioFileReader{r: eofReader{}, s: noopStdinStat}}
noopStdout = &FileEntry{File: &stdioFileWriter{w: io.Discard, s: noopStdoutStat}}
noopStderr = &FileEntry{File: &stdioFileWriter{w: io.Discard, s: noopStderrStat}}
)
//go:embed testdata
var testdata embed.FS
func TestNewFSContext(t *testing.T) {
embedFS, err := fs.Sub(testdata, "testdata")
require.NoError(t, err)
dirfs, err := syscallfs.NewDirFS(".", "/")
require.NoError(t, err)
// Test various usual configuration for the file system.
tests := []struct {
name string
fs syscallfs.FS
}{
{
name: "embed.FS",
fs: syscallfs.Adapt(embedFS, "/"),
},
{
name: "syscallfs.NewDirFS",
// Don't use "testdata" because it may not be present in
// cross-architecture (a.k.a. scratch) build containers.
fs: dirfs,
},
{
name: "syscallfs.NewReadFS",
fs: syscallfs.NewReadFS(dirfs),
},
{
name: "fstest.MapFS",
fs: syscallfs.Adapt(fstest.MapFS{}, "/"),
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(b *testing.T) {
fsc, err := NewFSContext(nil, nil, nil, tc.fs)
require.NoError(t, err)
defer fsc.Close(testCtx)
preopenedDir, _ := fsc.openedFiles.Lookup(FdPreopen)
preopenedPath := fsc.fs.GuestDir()
require.Equal(t, tc.fs, fsc.fs)
require.NotNil(t, preopenedDir)
require.Equal(t, "", preopenedDir.Name)
// Verify that each call to OpenFile returns a different file
// descriptor.
f1, err := fsc.OpenFile(preopenedPath, 0, 0)
require.NoError(t, err)
require.NotEqual(t, FdPreopen, f1)
// Verify that file descriptors are reused.
//
// Note that this specific behavior is not required by WASI which
// only documents that file descriptor numbers will be selected
// randomly and applications should not rely on them. We added this
// test to ensure that our implementation properly reuses descriptor
// numbers but if we were to change the reuse strategy, this test
// would likely break and need to be updated.
require.NoError(t, fsc.CloseFile(f1))
f2, err := fsc.OpenFile(preopenedPath, 0, 0)
require.NoError(t, err)
require.Equal(t, f1, f2)
})
}
}
func TestUnimplementedFSContext(t *testing.T) {
testFS, err := NewFSContext(nil, nil, nil, syscallfs.UnimplementedFS{})
require.NoError(t, err)
expected := &FSContext{fs: syscallfs.UnimplementedFS{}}
expected.openedFiles.Insert(noopStdin)
expected.openedFiles.Insert(noopStdout)
expected.openedFiles.Insert(noopStderr)
t.Run("Close closes", func(t *testing.T) {
err := testFS.Close(testCtx)
require.NoError(t, err)
// Closes opened files
require.Equal(t, &FSContext{fs: syscallfs.UnimplementedFS{}}, testFS)
})
}
func TestContext_Close(t *testing.T) {
testFS := syscallfs.Adapt(testfs.FS{"foo": &testfs.File{}}, "/")
fsc, err := NewFSContext(nil, nil, nil, testFS)
require.NoError(t, err)
// Verify base case
require.Equal(t, 1+FdPreopen, uint32(fsc.openedFiles.Len()))
_, err = fsc.OpenFile("foo", os.O_RDONLY, 0)
require.NoError(t, err)
require.Equal(t, 2+FdPreopen, uint32(fsc.openedFiles.Len()))
// Closing should not err.
require.NoError(t, fsc.Close(testCtx))
// Verify our intended side-effect
require.Zero(t, fsc.openedFiles.Len())
// Verify no error closing again.
require.NoError(t, fsc.Close(testCtx))
}
func TestContext_Close_Error(t *testing.T) {
file := &testfs.File{CloseErr: errors.New("error closing")}
testFS := syscallfs.Adapt(testfs.FS{"foo": file}, "/")
fsc, err := NewFSContext(nil, nil, nil, testFS)
require.NoError(t, err)
// open another file
_, err = fsc.OpenFile("foo", os.O_RDONLY, 0)
require.NoError(t, err)
require.EqualError(t, fsc.Close(testCtx), "error closing")
// Paths should clear even under error
require.Zero(t, fsc.openedFiles.Len(), "expected no opened files")
}