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

View File

@@ -1,12 +1,11 @@
package wazero package wazero
import ( import (
"bytes"
"context" "context"
"crypto/rand"
_ "embed" _ "embed"
"io"
"math"
"testing" "testing"
"time"
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/fstest" "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 // TestModuleConfig_toSysContext only tests the cases that change the inputs to
// sys.NewContext. // sys.NewContext.
func TestModuleConfig_toSysContext(t *testing.T) { 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 := 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 { tests := []struct {
name string name string
input ModuleConfig input func() (mc ModuleConfig, verify func(t *testing.T, sys *internalsys.Context))
expected *internalsys.Context
}{ }{
{ {
name: "empty", name: "empty",
input: base, input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
expected: requireSysContext(t, return base, func(t *testing.T, sys *internalsys.Context) { require.NotNil(t, sys) }
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: "WithArgs", name: "WithNanotime",
input: base.WithArgs("a", "bc"), input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
expected: requireSysContext(t, config := base.WithNanotime(func() int64 { return 1234567 }, 54321)
math.MaxUint32, // max return config, func(t *testing.T, sys *internalsys.Context) {
[]string{"a", "bc"}, // args require.Equal(t, 1234567, int(sys.Nanotime()))
nil, // environ require.Equal(t, 54321, int(sys.NanotimeResolution()))
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. name: "WithSysNanotime",
input: base.WithArgs("", "bc"), input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
expected: requireSysContext(t, config := base.WithSysNanotime()
math.MaxUint32, // max return config, func(t *testing.T, sys *internalsys.Context) {
[]string{"", "bc"}, // args require.Equal(t, int(1), int(sys.NanotimeResolution()))
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: "WithArgs second call overwrites", name: "WithWalltime",
input: base.WithArgs("a", "bc").WithArgs("bc", "a"), input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
expected: requireSysContext(t, config := base.WithWalltime(func() (sec int64, nsec int32) { return 5, 10 }, 54321)
math.MaxUint32, // max return config, func(t *testing.T, sys *internalsys.Context) {
[]string{"bc", "a"}, // args actualSec, actualNano := sys.Walltime()
nil, // environ require.Equal(t, 5, int(actualSec))
nil, // stdin require.Equal(t, 10, int(actualNano))
nil, // stdout require.Equal(t, 54321, int(sys.WalltimeResolution()))
nil, // stderr }
nil, // randSource },
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
}, },
{ {
name: "WithEnv", name: "WithSysWalltime",
input: base.WithEnv("a", "b"), input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
expected: requireSysContext(t, config := base.WithSysWalltime()
math.MaxUint32, // max return config, func(t *testing.T, sys *internalsys.Context) {
nil, // args require.Equal(t, int(time.Microsecond.Nanoseconds()), int(sys.WalltimeResolution()))
[]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: "WithEnv empty value", name: "WithArgs empty",
input: base.WithEnv("a", ""), input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
expected: requireSysContext(t, config := base.WithArgs()
math.MaxUint32, // max return config, func(t *testing.T, sys *internalsys.Context) {
nil, // args args := sys.Args()
[]string{"a="}, // environ require.Equal(t, 0, len(args))
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", name: "WithArgs",
input: base.WithEnv("a", "b").WithEnv("c", "de"), input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
expected: requireSysContext(t, config := base.WithArgs("a", "bc")
math.MaxUint32, // max return config, func(t *testing.T, sys *internalsys.Context) {
nil, // args args := sys.Args()
[]string{"a=b", "c=de"}, // environ require.Equal(t, 2, len(args))
nil, // stdin require.Equal(t, "a", string(args[0]))
nil, // stdout require.Equal(t, "bc", string(args[1]))
nil, // stderr }
nil, // randSource },
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
}, },
{ {
name: "WithEnv overwrites", name: "WithArgs empty ok", // Particularly argv[0] can be empty, and we have no rules about others.
input: base.WithEnv("a", "bc").WithEnv("c", "de").WithEnv("a", "de"), input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
expected: requireSysContext(t, config := base.WithArgs("", "bc")
math.MaxUint32, // max return config, func(t *testing.T, sys *internalsys.Context) {
nil, // args args := sys.Args()
[]string{"a=de", "c=de"}, // environ require.Equal(t, 2, len(args))
nil, // stdin require.Equal(t, "", string(args[0]))
nil, // stdout require.Equal(t, "bc", string(args[1]))
nil, // stderr }
nil, // randSource },
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
}, },
{ {
name: "WithEnv twice", name: "WithArgs second call overwrites",
input: base.WithEnv("a", "b").WithEnv("c", "de"), input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
expected: requireSysContext(t, config := base.WithArgs("a", "bc").WithArgs("bc", "a")
math.MaxUint32, // max return config, func(t *testing.T, sys *internalsys.Context) {
nil, // args args := sys.Args()
[]string{"a=b", "c=de"}, // environ require.Equal(t, 2, len(args))
nil, // stdin require.Equal(t, "bc", string(args[0]))
nil, // stdout require.Equal(t, "a", string(args[1]))
nil, // stderr }
nil, // randSource },
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
}, },
{ {
name: "WithFS", name: "WithEnv",
input: base.WithFS(testFS), input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
expected: requireSysContext(t, config := base.WithEnv("a", "b")
math.MaxUint32, // max return config, func(t *testing.T, sys *internalsys.Context) {
nil, // args envs := sys.Environ()
nil, // environ require.Equal(t, 1, len(envs))
nil, // stdin require.Equal(t, "a=b", string(envs[0]))
nil, // stdout }
nil, // stderr },
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
sysfs.Adapt(testFS),
),
}, },
{ {
name: "WithFS overwrites", name: "WithEnv empty value",
input: base.WithFS(testFS).WithFS(testFS2), input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
expected: requireSysContext(t, config := base.WithEnv("a", "")
math.MaxUint32, // max return config, func(t *testing.T, sys *internalsys.Context) {
nil, // args envs := sys.Environ()
nil, // environ require.Equal(t, 1, len(envs))
nil, // stdin require.Equal(t, "a=", string(envs[0]))
nil, // stdout }
nil, // stderr },
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
sysfs.Adapt(testFS2), // fs
),
}, },
{ {
name: "WithFS nil", name: "WithEnv twice",
input: base.WithFS(nil), input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
expected: requireSysContext(t, config := base.WithEnv("a", "b").WithEnv("c", "de")
math.MaxUint32, // max return config, func(t *testing.T, sys *internalsys.Context) {
nil, // args envs := sys.Environ()
nil, // environ require.Equal(t, 2, len(envs))
nil, // stdin require.Equal(t, "a=b", string(envs[0]))
nil, // stdout require.Equal(t, "c=de", string(envs[1]))
nil, // stderr }
nil, // randSource },
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // osyield
nil, // fs
),
}, },
{ {
name: "WithRandSource", name: "WithEnv overwrites",
input: base.WithRandSource(rand.Reader), input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
expected: requireSysContext(t, config := base.WithEnv("a", "bc").WithEnv("c", "de").WithEnv("a", "ff")
math.MaxUint32, // max return config, func(t *testing.T, sys *internalsys.Context) {
nil, // args envs := sys.Environ()
nil, // environ require.Equal(t, 2, len(envs))
nil, // stdin require.Equal(t, "a=ff", string(envs[0]))
nil, // stdout require.Equal(t, "c=de", string(envs[1]))
nil, // stderr }
rand.Reader, // randSource },
&wt, 1, // walltime, walltimeResolution },
&nt, 1, // nanotime, nanotimeResolution {
nil, // nanosleep name: "WithEnv twice",
nil, // osyield input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
nil, // fs 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 tc := tt
t.Run(tc.name, func(t *testing.T) { 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.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) 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. // NewFakeWalltime implements sys.Walltime with FakeEpochNanos that increases by 1ms each reading.
// See /RATIONALE.md // See /RATIONALE.md
func NewFakeWalltime() *sys.Walltime { func NewFakeWalltime() sys.Walltime {
// AddInt64 returns the new value. Adjust so the first reading will be FakeEpochNanos // AddInt64 returns the new value. Adjust so the first reading will be FakeEpochNanos
t := FakeEpochNanos - ms t := FakeEpochNanos - ms
var wt sys.Walltime = func() (sec int64, nsec int32) { return func() (sec int64, nsec int32) {
wt := atomic.AddInt64(&t, ms) wt := atomic.AddInt64(&t, ms)
return wt / 1e9, int32(wt % 1e9) return wt / 1e9, int32(wt % 1e9)
} }
return &wt
} }
// NewFakeNanotime implements sys.Nanotime that increases by 1ms each reading. // NewFakeNanotime implements sys.Nanotime that increases by 1ms each reading.
// See /RATIONALE.md // See /RATIONALE.md
func NewFakeNanotime() *sys.Nanotime { func NewFakeNanotime() sys.Nanotime {
// AddInt64 returns the new value. Adjust so the first reading will be zero. // AddInt64 returns the new value. Adjust so the first reading will be zero.
t := int64(0) - ms t := int64(0) - ms
var nt sys.Nanotime = func() int64 { return func() int64 {
return atomic.AddInt64(&t, ms) return atomic.AddInt64(&t, ms)
} }
return &nt
} }
// FakeNanosleep implements sys.Nanosleep by returning without sleeping. // 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. // FakeOsyield implements sys.Osyield by returning without yielding.
func FakeOsyield() {} var FakeOsyield = sys.Osyield(func() {})
// Walltime implements sys.Walltime with time.Now. // Walltime implements sys.Walltime with time.Now.
// //

View File

@@ -12,22 +12,22 @@ func Test_NewFakeWalltime(t *testing.T) {
wt := NewFakeWalltime() wt := NewFakeWalltime()
// Base should be the same as FakeEpochNanos // Base should be the same as FakeEpochNanos
sec, nsec := (*wt)() sec, nsec := wt()
ft := time.UnixMicro(FakeEpochNanos / time.Microsecond.Nanoseconds()).UTC() ft := time.UnixMicro(FakeEpochNanos / time.Microsecond.Nanoseconds()).UTC()
require.Equal(t, ft, time.Unix(sec, int64(nsec)).UTC()) require.Equal(t, ft, time.Unix(sec, int64(nsec)).UTC())
// next reading should increase by 1ms // 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()) require.Equal(t, ft.Add(time.Millisecond), time.Unix(sec, int64(nsec)).UTC())
} }
func Test_NewFakeNanotime(t *testing.T) { func Test_NewFakeNanotime(t *testing.T) {
nt := NewFakeNanotime() nt := NewFakeNanotime()
require.Equal(t, int64(0), (*nt)()) require.Equal(t, int64(0), nt())
// next reading should increase by 1ms // 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) { 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 // If `preopened` is not sysfs.UnimplementedFS, it is inserted into
// the file descriptor table as FdPreopen. // the file descriptor table as FdPreopen.
func NewFSContext(stdin io.Reader, stdout, stderr io.Writer, rootFS sysfs.FS) (fsc *FSContext, err error) { func (c *Context) NewFSContext(stdin io.Reader, stdout, stderr io.Writer, rootFS sysfs.FS) (err error) {
fsc = &FSContext{rootFS: rootFS} c.fsc.rootFS = rootFS
inReader, err := stdinReader(stdin) inReader, err := stdinReader(stdin)
if err != nil { if err != nil {
return nil, err return err
} }
fsc.openedFiles.Insert(inReader) c.fsc.openedFiles.Insert(inReader)
outWriter, err := stdioWriter(stdout, noopStdoutStat) outWriter, err := stdioWriter(stdout, noopStdoutStat)
if err != nil { if err != nil {
return nil, err return err
} }
fsc.openedFiles.Insert(outWriter) c.fsc.openedFiles.Insert(outWriter)
errWriter, err := stdioWriter(stderr, noopStderrStat) errWriter, err := stdioWriter(stderr, noopStderrStat)
if err != nil { if err != nil {
return nil, err return err
} }
fsc.openedFiles.Insert(errWriter) c.fsc.openedFiles.Insert(errWriter)
if _, ok := rootFS.(sysfs.UnimplementedFS); ok { if _, ok := rootFS.(sysfs.UnimplementedFS); ok {
return fsc, nil return nil
} }
if comp, ok := rootFS.(*sysfs.CompositeFS); ok { if comp, ok := rootFS.(*sysfs.CompositeFS); ok {
preopens := comp.FS() preopens := comp.FS()
for i, p := range comp.GuestPaths() { for i, p := range comp.GuestPaths() {
fsc.openedFiles.Insert(&FileEntry{ c.fsc.openedFiles.Insert(&FileEntry{
FS: preopens[i], FS: preopens[i],
Name: p, Name: p,
IsPreopen: true, IsPreopen: true,
@@ -285,7 +285,7 @@ func NewFSContext(stdin io.Reader, stdout, stderr io.Writer, rootFS sysfs.FS) (f
}) })
} }
} else { } else {
fsc.openedFiles.Insert(&FileEntry{ c.fsc.openedFiles.Insert(&FileEntry{
FS: rootFS, FS: rootFS,
Name: "/", Name: "/",
IsPreopen: true, 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) { func stdinReader(r io.Reader) (*FileEntry, error) {

View File

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

View File

@@ -17,16 +17,14 @@ type Context struct {
args, environ [][]byte args, environ [][]byte
argsSize, environSize uint32 argsSize, environSize uint32
// Note: Using function pointers here keeps them stable for tests. walltime sys.Walltime
walltime *sys.Walltime
walltimeResolution sys.ClockResolution walltimeResolution sys.ClockResolution
nanotime *sys.Nanotime nanotime sys.Nanotime
nanotimeResolution sys.ClockResolution nanotimeResolution sys.ClockResolution
nanosleep *sys.Nanosleep nanosleep sys.Nanosleep
osyield *sys.Osyield osyield sys.Osyield
randSource io.Reader randSource io.Reader
fsc *FSContext fsc FSContext
} }
// Args is like os.Args and defaults to nil. // Args is like os.Args and defaults to nil.
@@ -65,7 +63,7 @@ func (c *Context) EnvironSize() uint32 {
// Walltime implements platform.Walltime. // Walltime implements platform.Walltime.
func (c *Context) Walltime() (sec int64, nsec int32) { func (c *Context) Walltime() (sec int64, nsec int32) {
return (*(c.walltime))() return c.walltime()
} }
// WalltimeNanos returns platform.Walltime as epoch nanoseconds. // WalltimeNanos returns platform.Walltime as epoch nanoseconds.
@@ -81,7 +79,7 @@ func (c *Context) WalltimeResolution() sys.ClockResolution {
// Nanotime implements sys.Nanotime. // Nanotime implements sys.Nanotime.
func (c *Context) Nanotime() int64 { func (c *Context) Nanotime() int64 {
return (*(c.nanotime))() return c.nanotime()
} }
// NanotimeResolution returns resolution of Nanotime. // NanotimeResolution returns resolution of Nanotime.
@@ -91,17 +89,17 @@ func (c *Context) NanotimeResolution() sys.ClockResolution {
// Nanosleep implements sys.Nanosleep. // Nanosleep implements sys.Nanosleep.
func (c *Context) Nanosleep(ns int64) { func (c *Context) Nanosleep(ns int64) {
(*(c.nanosleep))(ns) c.nanosleep(ns)
} }
// Osyield implements sys.Osyield. // Osyield implements sys.Osyield.
func (c *Context) Osyield() { func (c *Context) Osyield() {
(*(c.osyield))() c.osyield()
} }
// FS returns the possibly empty (sysfs.UnimplementedFS) file system context. // FS returns the possibly empty (sysfs.UnimplementedFS) file system context.
func (c *Context) FS() *FSContext { func (c *Context) FS() *FSContext {
return c.fsc return &c.fsc
} }
// RandSource is a source of random bytes and defaults to a deterministic source. // 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 // 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 { 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 { 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)) 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. // 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. // Note: max is exposed for testing. max is only used for env/args validation.
func NewContext( func NewContext(
@@ -142,12 +136,12 @@ func NewContext(
stdin io.Reader, stdin io.Reader,
stdout, stderr io.Writer, stdout, stderr io.Writer,
randSource io.Reader, randSource io.Reader,
walltime *sys.Walltime, walltime sys.Walltime,
walltimeResolution sys.ClockResolution, walltimeResolution sys.ClockResolution,
nanotime *sys.Nanotime, nanotime sys.Nanotime,
nanotimeResolution sys.ClockResolution, nanotimeResolution sys.ClockResolution,
nanosleep *sys.Nanosleep, nanosleep sys.Nanosleep,
osyield *sys.Osyield, osyield sys.Osyield,
rootFS sysfs.FS, rootFS sysfs.FS,
) (sysCtx *Context, err error) { ) (sysCtx *Context, err error) {
sysCtx = &Context{args: args, environ: environ} sysCtx = &Context{args: args, environ: environ}
@@ -191,19 +185,19 @@ func NewContext(
if nanosleep != nil { if nanosleep != nil {
sysCtx.nanosleep = nanosleep sysCtx.nanosleep = nanosleep
} else { } else {
sysCtx.nanosleep = &ns sysCtx.nanosleep = platform.FakeNanosleep
} }
if osyield != nil { if osyield != nil {
sysCtx.osyield = osyield sysCtx.osyield = osyield
} else { } else {
sysCtx.osyield = &oy sysCtx.osyield = platform.FakeOsyield
} }
if rootFS != nil { if rootFS != nil {
sysCtx.fsc, err = NewFSContext(stdin, stdout, stderr, rootFS) err = sysCtx.NewFSContext(stdin, stdout, stderr, rootFS)
} else { } else {
sysCtx.fsc, err = NewFSContext(stdin, stdout, stderr, sysfs.UnimplementedFS{}) err = sysCtx.NewFSContext(stdin, stdout, stderr, sysfs.UnimplementedFS{})
} }
return return

View File

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