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>
155 lines
4.1 KiB
Go
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")
|
|
}
|