Stop using pointer of function pointers in sys.Context (#1301)

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
Takeshi Yoneda
2023-03-28 19:23:38 -07:00
committed by GitHub
parent e188b646f7
commit 2fc1fa9d79
8 changed files with 300 additions and 371 deletions

View File

@@ -609,12 +609,12 @@ type moduleConfig struct {
stdout io.Writer
stderr io.Writer
randSource io.Reader
walltime *sys.Walltime
walltime sys.Walltime
walltimeResolution sys.ClockResolution
nanotime *sys.Nanotime
nanotime sys.Nanotime
nanotimeResolution sys.ClockResolution
nanosleep *sys.Nanosleep
osyield *sys.Osyield
nanosleep sys.Nanosleep
osyield sys.Osyield
args [][]byte
// environ is pair-indexed to retain order similar to os.Environ.
environ [][]byte
@@ -728,7 +728,7 @@ func (c *moduleConfig) WithStdout(stdout io.Writer) ModuleConfig {
// WithWalltime implements ModuleConfig.WithWalltime
func (c *moduleConfig) WithWalltime(walltime sys.Walltime, resolution sys.ClockResolution) ModuleConfig {
ret := c.clone()
ret.walltime = &walltime
ret.walltime = walltime
ret.walltimeResolution = resolution
return ret
}
@@ -745,7 +745,7 @@ func (c *moduleConfig) WithSysWalltime() ModuleConfig {
// WithNanotime implements ModuleConfig.WithNanotime
func (c *moduleConfig) WithNanotime(nanotime sys.Nanotime, resolution sys.ClockResolution) ModuleConfig {
ret := c.clone()
ret.nanotime = &nanotime
ret.nanotime = nanotime
ret.nanotimeResolution = resolution
return ret
}
@@ -758,14 +758,14 @@ func (c *moduleConfig) WithSysNanotime() ModuleConfig {
// WithNanosleep implements ModuleConfig.WithNanosleep
func (c *moduleConfig) WithNanosleep(nanosleep sys.Nanosleep) ModuleConfig {
ret := *c // copy
ret.nanosleep = &nanosleep
ret.nanosleep = nanosleep
return &ret
}
// WithOsyield implements ModuleConfig.WithOsyield
func (c *moduleConfig) WithOsyield(osyield sys.Osyield) ModuleConfig {
ret := *c // copy
ret.osyield = &osyield
ret.osyield = osyield
return &ret
}

View File

@@ -1,12 +1,11 @@
package wazero
import (
"bytes"
"context"
"crypto/rand"
_ "embed"
"io"
"math"
"testing"
"time"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/fstest"
@@ -163,260 +162,214 @@ func TestModuleConfig(t *testing.T) {
// TestModuleConfig_toSysContext only tests the cases that change the inputs to
// sys.NewContext.
func TestModuleConfig_toSysContext(t *testing.T) {
// Always assigns clocks so that pointers are constant.
var wt sys.Walltime = func() (int64, int32) {
return 0, 0
}
var nt sys.Nanotime = func() int64 {
return 0
}
base := NewModuleConfig()
base.(*moduleConfig).walltime = &wt
base.(*moduleConfig).walltimeResolution = 1
base.(*moduleConfig).nanotime = &nt
base.(*moduleConfig).nanotimeResolution = 1
testFS := testfs.FS{}
testFS2 := testfs.FS{"/": &testfs.File{}}
tests := []struct {
name string
input ModuleConfig
expected *internalsys.Context
name string
input func() (mc ModuleConfig, verify func(t *testing.T, sys *internalsys.Context))
}{
{
name: "empty",
input: base,
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
nil, // environ
nil, // stdin
nil, // stdout
nil, // stderr
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
name: "empty",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
return base, func(t *testing.T, sys *internalsys.Context) { require.NotNil(t, sys) }
},
},
{
name: "WithArgs",
input: base.WithArgs("a", "bc"),
expected: requireSysContext(t,
math.MaxUint32, // max
[]string{"a", "bc"}, // args
nil, // environ
nil, // stdin
nil, // stdout
nil, // stderr
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
name: "WithNanotime",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithNanotime(func() int64 { return 1234567 }, 54321)
return config, func(t *testing.T, sys *internalsys.Context) {
require.Equal(t, 1234567, int(sys.Nanotime()))
require.Equal(t, 54321, int(sys.NanotimeResolution()))
}
},
},
{
name: "WithArgs empty ok", // Particularly argv[0] can be empty, and we have no rules about others.
input: base.WithArgs("", "bc"),
expected: requireSysContext(t,
math.MaxUint32, // max
[]string{"", "bc"}, // args
nil, // environ
nil, // stdin
nil, // stdout
nil, // stderr
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
name: "WithSysNanotime",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithSysNanotime()
return config, func(t *testing.T, sys *internalsys.Context) {
require.Equal(t, int(1), int(sys.NanotimeResolution()))
}
},
},
{
name: "WithArgs second call overwrites",
input: base.WithArgs("a", "bc").WithArgs("bc", "a"),
expected: requireSysContext(t,
math.MaxUint32, // max
[]string{"bc", "a"}, // args
nil, // environ
nil, // stdin
nil, // stdout
nil, // stderr
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
name: "WithWalltime",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithWalltime(func() (sec int64, nsec int32) { return 5, 10 }, 54321)
return config, func(t *testing.T, sys *internalsys.Context) {
actualSec, actualNano := sys.Walltime()
require.Equal(t, 5, int(actualSec))
require.Equal(t, 10, int(actualNano))
require.Equal(t, 54321, int(sys.WalltimeResolution()))
}
},
},
{
name: "WithEnv",
input: base.WithEnv("a", "b"),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
[]string{"a=b"}, // environ
nil, // stdin
nil, // stdout
nil, // stderr
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
name: "WithSysWalltime",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithSysWalltime()
return config, func(t *testing.T, sys *internalsys.Context) {
require.Equal(t, int(time.Microsecond.Nanoseconds()), int(sys.WalltimeResolution()))
}
},
},
{
name: "WithEnv empty value",
input: base.WithEnv("a", ""),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
[]string{"a="}, // environ
nil, // stdin
nil, // stdout
nil, // stderr
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
name: "WithArgs empty",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithArgs()
return config, func(t *testing.T, sys *internalsys.Context) {
args := sys.Args()
require.Equal(t, 0, len(args))
}
},
},
{
name: "WithEnv twice",
input: base.WithEnv("a", "b").WithEnv("c", "de"),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
[]string{"a=b", "c=de"}, // environ
nil, // stdin
nil, // stdout
nil, // stderr
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
name: "WithArgs",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithArgs("a", "bc")
return config, func(t *testing.T, sys *internalsys.Context) {
args := sys.Args()
require.Equal(t, 2, len(args))
require.Equal(t, "a", string(args[0]))
require.Equal(t, "bc", string(args[1]))
}
},
},
{
name: "WithEnv overwrites",
input: base.WithEnv("a", "bc").WithEnv("c", "de").WithEnv("a", "de"),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
[]string{"a=de", "c=de"}, // environ
nil, // stdin
nil, // stdout
nil, // stderr
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
name: "WithArgs empty ok", // Particularly argv[0] can be empty, and we have no rules about others.
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithArgs("", "bc")
return config, func(t *testing.T, sys *internalsys.Context) {
args := sys.Args()
require.Equal(t, 2, len(args))
require.Equal(t, "", string(args[0]))
require.Equal(t, "bc", string(args[1]))
}
},
},
{
name: "WithEnv twice",
input: base.WithEnv("a", "b").WithEnv("c", "de"),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
[]string{"a=b", "c=de"}, // environ
nil, // stdin
nil, // stdout
nil, // stderr
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
name: "WithArgs second call overwrites",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithArgs("a", "bc").WithArgs("bc", "a")
return config, func(t *testing.T, sys *internalsys.Context) {
args := sys.Args()
require.Equal(t, 2, len(args))
require.Equal(t, "bc", string(args[0]))
require.Equal(t, "a", string(args[1]))
}
},
},
{
name: "WithFS",
input: base.WithFS(testFS),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
nil, // environ
nil, // stdin
nil, // stdout
nil, // stderr
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
sysfs.Adapt(testFS),
),
name: "WithEnv",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithEnv("a", "b")
return config, func(t *testing.T, sys *internalsys.Context) {
envs := sys.Environ()
require.Equal(t, 1, len(envs))
require.Equal(t, "a=b", string(envs[0]))
}
},
},
{
name: "WithFS overwrites",
input: base.WithFS(testFS).WithFS(testFS2),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
nil, // environ
nil, // stdin
nil, // stdout
nil, // stderr
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
sysfs.Adapt(testFS2), // fs
),
name: "WithEnv empty value",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithEnv("a", "")
return config, func(t *testing.T, sys *internalsys.Context) {
envs := sys.Environ()
require.Equal(t, 1, len(envs))
require.Equal(t, "a=", string(envs[0]))
}
},
},
{
name: "WithFS nil",
input: base.WithFS(nil),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
nil, // environ
nil, // stdin
nil, // stdout
nil, // stderr
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
name: "WithEnv twice",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithEnv("a", "b").WithEnv("c", "de")
return config, func(t *testing.T, sys *internalsys.Context) {
envs := sys.Environ()
require.Equal(t, 2, len(envs))
require.Equal(t, "a=b", string(envs[0]))
require.Equal(t, "c=de", string(envs[1]))
}
},
},
{
name: "WithRandSource",
input: base.WithRandSource(rand.Reader),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
nil, // environ
nil, // stdin
nil, // stdout
nil, // stderr
rand.Reader, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
name: "WithEnv overwrites",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithEnv("a", "bc").WithEnv("c", "de").WithEnv("a", "ff")
return config, func(t *testing.T, sys *internalsys.Context) {
envs := sys.Environ()
require.Equal(t, 2, len(envs))
require.Equal(t, "a=ff", string(envs[0]))
require.Equal(t, "c=de", string(envs[1]))
}
},
},
{
name: "WithEnv twice",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithEnv("a", "b").WithEnv("c", "de")
return config, func(t *testing.T, sys *internalsys.Context) {
envs := sys.Environ()
require.Equal(t, 2, len(envs))
require.Equal(t, "a=b", string(envs[0]))
require.Equal(t, "c=de", string(envs[1]))
}
},
},
{
name: "WithFS",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
testFS := &testfs.FS{}
config := base.WithFS(testFS)
return config, func(t *testing.T, sys *internalsys.Context) {
rootfs := sys.FS().RootFS()
require.Equal(t, sysfs.Adapt(testFS), rootfs)
}
},
},
{
name: "WithFS overwrites",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
testFS, testFS2 := &testfs.FS{}, &testfs.FS{}
config := base.WithFS(testFS).WithFS(testFS2)
return config, func(t *testing.T, sys *internalsys.Context) {
rootfs := sys.FS().RootFS()
require.Equal(t, sysfs.Adapt(testFS2), rootfs)
}
},
},
{
name: "WithFS nil",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithFS(nil)
return config, func(t *testing.T, sys *internalsys.Context) {
rootfs := sys.FS().RootFS()
require.Equal(t, sysfs.Adapt(nil), rootfs)
}
},
},
{
name: "WithRandSource",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
r := bytes.NewReader([]byte{1, 2, 3, 4})
config := base.WithRandSource(r)
return config, func(t *testing.T, sys *internalsys.Context) {
actual := sys.RandSource()
require.Equal(t, r, actual)
}
},
},
{
name: "WithRandSource nil",
input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
config := base.WithRandSource(nil)
return config, func(t *testing.T, sys *internalsys.Context) {
actual := sys.RandSource()
require.Equal(t, platform.NewFakeRandSource(), actual)
}
},
},
}
@@ -424,9 +377,10 @@ func TestModuleConfig_toSysContext(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
sysCtx, err := tc.input.(*moduleConfig).toSysContext()
config, verify := tc.input()
actual, err := config.(*moduleConfig).toSysContext()
require.NoError(t, err)
require.Equal(t, tc.expected, sysCtx)
verify(t, actual)
})
}
}
@@ -756,34 +710,3 @@ func TestNewRuntimeConfig(t *testing.T) {
require.Equal(t, engineKindInterpreter, c.engineKind)
}
}
// requireSysContext ensures wasm.NewContext doesn't return an error, which makes it usable in test matrices.
func requireSysContext(
t *testing.T,
max uint32,
args, environ []string,
stdin io.Reader,
stdout, stderr io.Writer,
randSource io.Reader,
walltime *sys.Walltime, walltimeResolution sys.ClockResolution,
nanotime *sys.Nanotime, nanotimeResolution sys.ClockResolution,
nanosleep *sys.Nanosleep,
osyield *sys.Osyield,
fs sysfs.FS,
) *internalsys.Context {
sysCtx, err := internalsys.NewContext(
max,
toByteSlices(args),
toByteSlices(environ),
stdin,
stdout,
stderr,
randSource,
walltime, walltimeResolution,
nanotime, nanotimeResolution,
nanosleep, osyield,
fs,
)
require.NoError(t, err)
return sysCtx
}

View File

@@ -15,32 +15,30 @@ const (
// NewFakeWalltime implements sys.Walltime with FakeEpochNanos that increases by 1ms each reading.
// See /RATIONALE.md
func NewFakeWalltime() *sys.Walltime {
func NewFakeWalltime() sys.Walltime {
// AddInt64 returns the new value. Adjust so the first reading will be FakeEpochNanos
t := FakeEpochNanos - ms
var wt sys.Walltime = func() (sec int64, nsec int32) {
return func() (sec int64, nsec int32) {
wt := atomic.AddInt64(&t, ms)
return wt / 1e9, int32(wt % 1e9)
}
return &wt
}
// NewFakeNanotime implements sys.Nanotime that increases by 1ms each reading.
// See /RATIONALE.md
func NewFakeNanotime() *sys.Nanotime {
func NewFakeNanotime() sys.Nanotime {
// AddInt64 returns the new value. Adjust so the first reading will be zero.
t := int64(0) - ms
var nt sys.Nanotime = func() int64 {
return func() int64 {
return atomic.AddInt64(&t, ms)
}
return &nt
}
// FakeNanosleep implements sys.Nanosleep by returning without sleeping.
func FakeNanosleep(int64) {}
var FakeNanosleep = sys.Nanosleep(func(int64) {})
// FakeOsyield implements sys.Osyield by returning without yielding.
func FakeOsyield() {}
var FakeOsyield = sys.Osyield(func() {})
// Walltime implements sys.Walltime with time.Now.
//

View File

@@ -12,22 +12,22 @@ func Test_NewFakeWalltime(t *testing.T) {
wt := NewFakeWalltime()
// Base should be the same as FakeEpochNanos
sec, nsec := (*wt)()
sec, nsec := wt()
ft := time.UnixMicro(FakeEpochNanos / time.Microsecond.Nanoseconds()).UTC()
require.Equal(t, ft, time.Unix(sec, int64(nsec)).UTC())
// next reading should increase by 1ms
sec, nsec = (*wt)()
sec, nsec = wt()
require.Equal(t, ft.Add(time.Millisecond), time.Unix(sec, int64(nsec)).UTC())
}
func Test_NewFakeNanotime(t *testing.T) {
nt := NewFakeNanotime()
require.Equal(t, int64(0), (*nt)())
require.Equal(t, int64(0), nt())
// next reading should increase by 1ms
require.Equal(t, int64(time.Millisecond), (*nt)())
require.Equal(t, int64(time.Millisecond), nt())
}
func Test_Walltime(t *testing.T) {

View File

@@ -252,32 +252,32 @@ type FileTable = descriptor.Table[uint32, *FileEntry]
//
// If `preopened` is not sysfs.UnimplementedFS, it is inserted into
// the file descriptor table as FdPreopen.
func NewFSContext(stdin io.Reader, stdout, stderr io.Writer, rootFS sysfs.FS) (fsc *FSContext, err error) {
fsc = &FSContext{rootFS: rootFS}
func (c *Context) NewFSContext(stdin io.Reader, stdout, stderr io.Writer, rootFS sysfs.FS) (err error) {
c.fsc.rootFS = rootFS
inReader, err := stdinReader(stdin)
if err != nil {
return nil, err
return err
}
fsc.openedFiles.Insert(inReader)
c.fsc.openedFiles.Insert(inReader)
outWriter, err := stdioWriter(stdout, noopStdoutStat)
if err != nil {
return nil, err
return err
}
fsc.openedFiles.Insert(outWriter)
c.fsc.openedFiles.Insert(outWriter)
errWriter, err := stdioWriter(stderr, noopStderrStat)
if err != nil {
return nil, err
return err
}
fsc.openedFiles.Insert(errWriter)
c.fsc.openedFiles.Insert(errWriter)
if _, ok := rootFS.(sysfs.UnimplementedFS); ok {
return fsc, nil
return nil
}
if comp, ok := rootFS.(*sysfs.CompositeFS); ok {
preopens := comp.FS()
for i, p := range comp.GuestPaths() {
fsc.openedFiles.Insert(&FileEntry{
c.fsc.openedFiles.Insert(&FileEntry{
FS: preopens[i],
Name: p,
IsPreopen: true,
@@ -285,7 +285,7 @@ func NewFSContext(stdin io.Reader, stdout, stderr io.Writer, rootFS sysfs.FS) (f
})
}
} else {
fsc.openedFiles.Insert(&FileEntry{
c.fsc.openedFiles.Insert(&FileEntry{
FS: rootFS,
Name: "/",
IsPreopen: true,
@@ -293,7 +293,7 @@ func NewFSContext(stdin io.Reader, stdout, stderr io.Writer, rootFS sysfs.FS) (f
})
}
return fsc, nil
return nil
}
func stdinReader(r io.Reader) (*FileEntry, error) {

View File

@@ -66,8 +66,10 @@ func TestNewFSContext(t *testing.T) {
tc := tt
t.Run(tc.name, func(b *testing.T) {
fsc, err := NewFSContext(nil, nil, nil, tc.fs)
c := Context{}
err := c.NewFSContext(nil, nil, nil, tc.fs)
require.NoError(t, err)
fsc := c.fsc
defer fsc.Close(testCtx)
preopenedDir, _ := fsc.openedFiles.Lookup(FdPreopen)
@@ -102,8 +104,10 @@ func TestFSContext_CloseFile(t *testing.T) {
require.NoError(t, err)
testFS := sysfs.Adapt(embedFS)
fsc, err := NewFSContext(nil, nil, nil, testFS)
c := Context{}
err = c.NewFSContext(nil, nil, nil, testFS)
require.NoError(t, err)
fsc := c.fsc
defer fsc.Close(testCtx)
fdToClose, errno := fsc.OpenFile(testFS, "empty.txt", os.O_RDONLY, 0)
@@ -132,7 +136,10 @@ func TestFSContext_CloseFile(t *testing.T) {
}
func TestUnimplementedFSContext(t *testing.T) {
testFS, err := NewFSContext(nil, nil, nil, sysfs.UnimplementedFS{})
c := Context{}
err := c.NewFSContext(nil, nil, nil, sysfs.UnimplementedFS{})
require.NoError(t, err)
testFS := &c.fsc
require.NoError(t, err)
expected := &FSContext{rootFS: sysfs.UnimplementedFS{}}
@@ -159,8 +166,10 @@ func TestCompositeFSContext(t *testing.T) {
rootFS, err := sysfs.NewRootFS([]sysfs.FS{testFS2, testFS1}, []string{"/tmp", "/"})
require.NoError(t, err)
testFS, err := NewFSContext(nil, nil, nil, rootFS)
c := Context{}
err = c.NewFSContext(nil, nil, nil, rootFS)
require.NoError(t, err)
testFS := &c.fsc
// Ensure the pre-opens have exactly the name specified, and are in order.
preopen3, ok := testFS.openedFiles.Lookup(3)
@@ -182,8 +191,10 @@ func TestCompositeFSContext(t *testing.T) {
func TestContext_Close(t *testing.T) {
testFS := sysfs.Adapt(testfs.FS{"foo": &testfs.File{}})
fsc, err := NewFSContext(nil, nil, nil, testFS)
c := Context{}
err := c.NewFSContext(nil, nil, nil, testFS)
require.NoError(t, err)
fsc := c.fsc
// Verify base case
require.Equal(t, 1+FdPreopen, uint32(fsc.openedFiles.Len()))
@@ -207,8 +218,10 @@ func TestContext_Close_Error(t *testing.T) {
testFS := sysfs.Adapt(testfs.FS{"foo": file})
fsc, err := NewFSContext(nil, nil, nil, testFS)
c := Context{}
err := c.NewFSContext(nil, nil, nil, testFS)
require.NoError(t, err)
fsc := c.fsc
// open another file
_, errno := fsc.OpenFile(testFS, "foo", os.O_RDONLY, 0)
@@ -228,7 +241,11 @@ func TestFSContext_ReOpenDir(t *testing.T) {
errno := dirFs.Mkdir(dirName, 0o700)
require.Zero(t, errno)
fsc, err := NewFSContext(nil, nil, nil, dirFs)
c := Context{}
err := c.NewFSContext(nil, nil, nil, dirFs)
require.NoError(t, err)
fsc := c.fsc
require.NoError(t, err)
defer func() {
require.NoError(t, fsc.Close(context.Background()))
@@ -275,45 +292,48 @@ func TestFSContext_Renumber(t *testing.T) {
errno := dirFs.Mkdir(dirName, 0o700)
require.Zero(t, errno)
c, err := NewFSContext(nil, nil, nil, dirFs)
c := Context{}
err := c.NewFSContext(nil, nil, nil, dirFs)
require.NoError(t, err)
fsc := c.fsc
defer func() {
require.NoError(t, c.Close(context.Background()))
require.NoError(t, fsc.Close(context.Background()))
}()
for _, toFd := range []uint32{10, 100, 100} {
fromFd, errno := c.OpenFile(dirFs, dirName, os.O_RDONLY, 0)
fromFd, errno := fsc.OpenFile(dirFs, dirName, os.O_RDONLY, 0)
require.Zero(t, errno)
prevDirFile, ok := c.LookupFile(fromFd)
prevDirFile, ok := fsc.LookupFile(fromFd)
require.True(t, ok)
require.Zero(t, c.Renumber(fromFd, toFd))
require.Zero(t, fsc.Renumber(fromFd, toFd))
renumberedDirFile, ok := c.LookupFile(toFd)
renumberedDirFile, ok := fsc.LookupFile(toFd)
require.True(t, ok)
require.Equal(t, prevDirFile, renumberedDirFile)
// Previous file descriptor shouldn't be used.
_, ok = c.LookupFile(fromFd)
_, ok = fsc.LookupFile(fromFd)
require.False(t, ok)
}
t.Run("errors", func(t *testing.T) {
// Sanity check for 3 being preopen.
preopen, ok := c.LookupFile(3)
preopen, ok := fsc.LookupFile(3)
require.True(t, ok)
require.True(t, preopen.IsPreopen)
// From is preopen.
require.Equal(t, syscall.ENOTSUP, c.Renumber(3, 100))
require.Equal(t, syscall.ENOTSUP, fsc.Renumber(3, 100))
// From does not exist.
require.Equal(t, syscall.EBADF, c.Renumber(12345, 3))
require.Equal(t, syscall.EBADF, fsc.Renumber(12345, 3))
// Both are preopen.
require.Equal(t, syscall.ENOTSUP, c.Renumber(3, 3))
require.Equal(t, syscall.ENOTSUP, fsc.Renumber(3, 3))
})
}
@@ -324,36 +344,41 @@ func TestFSContext_ChangeOpenFlag(t *testing.T) {
const fileName = "dir"
require.NoError(t, os.WriteFile(path.Join(tmpDir, fileName), []byte("0123456789"), 0o600))
c, errno := NewFSContext(nil, nil, nil, dirFs)
require.NoError(t, errno)
c := Context{}
err := c.NewFSContext(nil, nil, nil, dirFs)
require.NoError(t, err)
fsc := c.fsc
defer func() {
require.NoError(t, c.Close(context.Background()))
require.NoError(t, fsc.Close(context.Background()))
}()
// Without APPEND.
fd, errno := c.OpenFile(dirFs, fileName, os.O_RDWR, 0o600)
fd, errno := fsc.OpenFile(dirFs, fileName, os.O_RDWR, 0o600)
require.Zero(t, errno)
f0, ok := c.openedFiles.Lookup(fd)
f0, ok := fsc.openedFiles.Lookup(fd)
require.True(t, ok)
require.Equal(t, f0.openFlag&syscall.O_APPEND, 0)
// Set the APPEND flag.
require.Zero(t, c.ChangeOpenFlag(fd, syscall.O_APPEND))
f1, ok := c.openedFiles.Lookup(fd)
require.Zero(t, fsc.ChangeOpenFlag(fd, syscall.O_APPEND))
f1, ok := fsc.openedFiles.Lookup(fd)
require.True(t, ok)
require.Equal(t, f1.openFlag&syscall.O_APPEND, syscall.O_APPEND)
// Remove the APPEND flag.
require.Zero(t, c.ChangeOpenFlag(fd, 0))
f2, ok := c.openedFiles.Lookup(fd)
require.Zero(t, fsc.ChangeOpenFlag(fd, 0))
f2, ok := fsc.openedFiles.Lookup(fd)
require.True(t, ok)
require.Equal(t, f2.openFlag&syscall.O_APPEND, 0)
}
func TestWriterForFile(t *testing.T) {
testFS, err := NewFSContext(nil, nil, nil, sysfs.UnimplementedFS{})
c := Context{}
err := c.NewFSContext(nil, nil, nil, sysfs.UnimplementedFS{})
require.NoError(t, err)
testFS := &c.fsc
require.Nil(t, WriterForFile(testFS, FdStdin))
require.Equal(t, noopStdout.File, WriterForFile(testFS, FdStdout))

View File

@@ -17,16 +17,14 @@ type Context struct {
args, environ [][]byte
argsSize, environSize uint32
// Note: Using function pointers here keeps them stable for tests.
walltime *sys.Walltime
walltime sys.Walltime
walltimeResolution sys.ClockResolution
nanotime *sys.Nanotime
nanotime sys.Nanotime
nanotimeResolution sys.ClockResolution
nanosleep *sys.Nanosleep
osyield *sys.Osyield
nanosleep sys.Nanosleep
osyield sys.Osyield
randSource io.Reader
fsc *FSContext
fsc FSContext
}
// Args is like os.Args and defaults to nil.
@@ -65,7 +63,7 @@ func (c *Context) EnvironSize() uint32 {
// Walltime implements platform.Walltime.
func (c *Context) Walltime() (sec int64, nsec int32) {
return (*(c.walltime))()
return c.walltime()
}
// WalltimeNanos returns platform.Walltime as epoch nanoseconds.
@@ -81,7 +79,7 @@ func (c *Context) WalltimeResolution() sys.ClockResolution {
// Nanotime implements sys.Nanotime.
func (c *Context) Nanotime() int64 {
return (*(c.nanotime))()
return c.nanotime()
}
// NanotimeResolution returns resolution of Nanotime.
@@ -91,17 +89,17 @@ func (c *Context) NanotimeResolution() sys.ClockResolution {
// Nanosleep implements sys.Nanosleep.
func (c *Context) Nanosleep(ns int64) {
(*(c.nanosleep))(ns)
c.nanosleep(ns)
}
// Osyield implements sys.Osyield.
func (c *Context) Osyield() {
(*(c.osyield))()
c.osyield()
}
// FS returns the possibly empty (sysfs.UnimplementedFS) file system context.
func (c *Context) FS() *FSContext {
return c.fsc
return &c.fsc
}
// RandSource is a source of random bytes and defaults to a deterministic source.
@@ -120,6 +118,8 @@ func (eofReader) Read([]byte) (int, error) {
}
// DefaultContext returns Context with no values set except a possible nil fs.FS
//
// This is only used for testing.
func DefaultContext(fs sysfs.FS) *Context {
if sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil, nil, fs); err != nil {
panic(fmt.Errorf("BUG: DefaultContext should never error: %w", err))
@@ -128,12 +128,6 @@ func DefaultContext(fs sysfs.FS) *Context {
}
}
var (
_ = DefaultContext(nil) // Force panic on bug.
ns sys.Nanosleep = platform.FakeNanosleep
oy sys.Osyield = platform.FakeOsyield
)
// NewContext is a factory function which helps avoid needing to know defaults or exporting all fields.
// Note: max is exposed for testing. max is only used for env/args validation.
func NewContext(
@@ -142,12 +136,12 @@ func NewContext(
stdin io.Reader,
stdout, stderr io.Writer,
randSource io.Reader,
walltime *sys.Walltime,
walltime sys.Walltime,
walltimeResolution sys.ClockResolution,
nanotime *sys.Nanotime,
nanotime sys.Nanotime,
nanotimeResolution sys.ClockResolution,
nanosleep *sys.Nanosleep,
osyield *sys.Osyield,
nanosleep sys.Nanosleep,
osyield sys.Osyield,
rootFS sysfs.FS,
) (sysCtx *Context, err error) {
sysCtx = &Context{args: args, environ: environ}
@@ -191,19 +185,19 @@ func NewContext(
if nanosleep != nil {
sysCtx.nanosleep = nanosleep
} else {
sysCtx.nanosleep = &ns
sysCtx.nanosleep = platform.FakeNanosleep
}
if osyield != nil {
sysCtx.osyield = osyield
} else {
sysCtx.osyield = &oy
sysCtx.osyield = platform.FakeOsyield
}
if rootFS != nil {
sysCtx.fsc, err = NewFSContext(stdin, stdout, stderr, rootFS)
err = sysCtx.NewFSContext(stdin, stdout, stderr, rootFS)
} else {
sysCtx.fsc, err = NewFSContext(stdin, stdout, stderr, sysfs.UnimplementedFS{})
err = sysCtx.NewFSContext(stdin, stdout, stderr, sysfs.UnimplementedFS{})
}
return

View File

@@ -15,15 +15,6 @@ import (
"github.com/tetratelabs/wazero/sys"
)
func TestContext_FS(t *testing.T) {
sysCtx := DefaultContext(nil)
fsc, err := NewFSContext(nil, nil, nil, sysfs.UnimplementedFS{})
require.NoError(t, err)
require.Equal(t, fsc, sysCtx.FS())
}
func TestContext_WalltimeNanos(t *testing.T) {
sysCtx := DefaultContext(nil)
@@ -60,11 +51,9 @@ func TestDefaultSysContext(t *testing.T) {
require.Equal(t, sys.ClockResolution(1_000), sysCtx.WalltimeResolution())
require.Zero(t, sysCtx.Nanotime()) // See above on functions.
require.Equal(t, sys.ClockResolution(1), sysCtx.NanotimeResolution())
require.Equal(t, &ns, sysCtx.nanosleep)
require.Equal(t, platform.FakeNanosleep, sysCtx.nanosleep)
require.Equal(t, platform.NewFakeRandSource(), sysCtx.RandSource())
expectedFS, _ := NewFSContext(nil, nil, nil, testFS)
expectedOpenedFiles := FileTable{}
expectedOpenedFiles.Insert(noopStdin)
expectedOpenedFiles.Insert(noopStdout)
@@ -75,9 +64,7 @@ func TestDefaultSysContext(t *testing.T) {
FS: testFS,
File: &lazyDir{fs: testFS},
})
require.Equal(t, expectedOpenedFiles, expectedFS.openedFiles)
require.Equal(t, expectedFS, sysCtx.FS())
require.Equal(t, expectedOpenedFiles, sysCtx.FS().openedFiles)
}
func TestFileEntry_cachedStat(t *testing.T) {
@@ -104,7 +91,9 @@ func TestFileEntry_cachedStat(t *testing.T) {
tc := tc
t.Run(tc.name, func(t *testing.T) {
fsc, _ := NewFSContext(nil, nil, nil, tc.fs)
c := Context{}
_ = c.NewFSContext(nil, nil, nil, tc.fs)
fsc := c.fsc
defer fsc.Close(testCtx)
f, ok := fsc.LookupFile(FdPreopen)
@@ -258,7 +247,7 @@ func TestNewContext_Environ(t *testing.T) {
func TestNewContext_Walltime(t *testing.T) {
tests := []struct {
name string
time *sys.Walltime
time sys.Walltime
resolution sys.ClockResolution
expectedErr string
}{
@@ -307,7 +296,7 @@ func TestNewContext_Walltime(t *testing.T) {
func TestNewContext_Nanotime(t *testing.T) {
tests := []struct {
name string
time *sys.Nanotime
time sys.Nanotime
resolution sys.ClockResolution
expectedErr string
}{
@@ -396,12 +385,12 @@ func TestNewContext_Nanosleep(t *testing.T) {
nil, // randSource
nil, 0, // Nanosleep, NanosleepResolution
nil, 0, // Nanosleep, NanosleepResolution
&aNs, // nanosleep
nil, // osyield
nil, // rootFS
aNs, // nanosleep
nil, // osyield
nil, // rootFS
)
require.Nil(t, err)
require.Equal(t, &aNs, sysCtx.nanosleep)
require.Equal(t, aNs, sysCtx.nanosleep)
}
func TestNewContext_Osyield(t *testing.T) {
@@ -417,9 +406,9 @@ func TestNewContext_Osyield(t *testing.T) {
nil, 0, // Nanosleep, NanosleepResolution
nil, 0, // Nanosleep, NanosleepResolution
nil, // nanosleep
&oy, // osyield
oy, // osyield
nil, // rootFS
)
require.Nil(t, err)
require.Equal(t, &oy, sysCtx.osyield)
require.Equal(t, oy, sysCtx.osyield)
}