From 2fc1fa9d79bdd75e99d9d13df83a7f1ad3e532d1 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Tue, 28 Mar 2023 19:23:38 -0700 Subject: [PATCH] Stop using pointer of function pointers in sys.Context (#1301) Signed-off-by: Takeshi Yoneda --- config.go | 16 +- config_test.go | 445 ++++++++++++++------------------- internal/platform/time.go | 14 +- internal/platform/time_test.go | 8 +- internal/sys/fs.go | 24 +- internal/sys/fs_test.go | 81 +++--- internal/sys/sys.go | 46 ++-- internal/sys/sys_test.go | 37 +-- 8 files changed, 300 insertions(+), 371 deletions(-) diff --git a/config.go b/config.go index ccec69f8..259b7f50 100644 --- a/config.go +++ b/config.go @@ -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 } diff --git a/config_test.go b/config_test.go index c0824a41..c8f2419e 100644 --- a/config_test.go +++ b/config_test.go @@ -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 -} diff --git a/internal/platform/time.go b/internal/platform/time.go index 5de23af1..fa9da1ac 100644 --- a/internal/platform/time.go +++ b/internal/platform/time.go @@ -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. // diff --git a/internal/platform/time_test.go b/internal/platform/time_test.go index 8c30fdea..8454a714 100644 --- a/internal/platform/time_test.go +++ b/internal/platform/time_test.go @@ -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) { diff --git a/internal/sys/fs.go b/internal/sys/fs.go index 29ed47b7..7070e15c 100644 --- a/internal/sys/fs.go +++ b/internal/sys/fs.go @@ -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) { diff --git a/internal/sys/fs_test.go b/internal/sys/fs_test.go index 26c58ff8..b7ec3514 100644 --- a/internal/sys/fs_test.go +++ b/internal/sys/fs_test.go @@ -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)) diff --git a/internal/sys/sys.go b/internal/sys/sys.go index 1c86bce9..a0b5dbd5 100644 --- a/internal/sys/sys.go +++ b/internal/sys/sys.go @@ -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 diff --git a/internal/sys/sys_test.go b/internal/sys/sys_test.go index b8c4903e..d4755943 100644 --- a/internal/sys/sys_test.go +++ b/internal/sys/sys_test.go @@ -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) }