Files
wazero/internal/sys/fs_test.go
Crypt Keeper 83e4b66659 Consolidates internal code to syscallfs (#1003)
This consolidates internal code to syscallfs, which removes the fs.FS
specific path rules, except when adapting one to syscallfs. For example,
this allows the underlying filesystem to decide if relative paths are
supported or not, as well any EINVAL related concerns.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2023-01-04 13:53:53 +08:00

155 lines
4.1 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{Name: noopStdinStat.Name(), File: &stdioFileReader{r: eofReader{}, s: noopStdinStat}}
noopStdout = &FileEntry{Name: noopStdoutStat.Name(), File: &stdioFileWriter{w: io.Discard, s: noopStdoutStat}}
noopStderr = &FileEntry{Name: noopStderrStat.Name(), 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)
rootFile, _ := fsc.openedFiles.Lookup(FdRoot)
require.Equal(t, tc.fs, fsc.fs)
require.NotNil(t, rootFile)
require.Equal(t, "/", rootFile.Name)
// Verify that each call to OpenFile returns a different file
// descriptor.
f1, err := fsc.OpenFile("/", 0, 0)
require.NoError(t, err)
require.NotEqual(t, FdRoot, 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("/", 0, 0)
require.NoError(t, err)
require.Equal(t, f1, f2)
})
}
}
func TestEmptyFSContext(t *testing.T) {
testFS, err := NewFSContext(nil, nil, nil, syscallfs.EmptyFS)
require.NoError(t, err)
expected := &FSContext{fs: syscallfs.EmptyFS}
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.EmptyFS}, 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+FdRoot, uint32(fsc.openedFiles.Len()))
_, err = fsc.OpenFile("foo", os.O_RDONLY, 0)
require.NoError(t, err)
require.Equal(t, 2+FdRoot, 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")
}