gojs: refactors out gojs.Config type (#1240)

In order to support more configuration, we should stop using context as
it is getting gnarly.

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Takeshi Yoneda
2023-03-14 21:27:47 -07:00
committed by GitHub
parent 00c53d19a2
commit f24a3f49a4
22 changed files with 228 additions and 142 deletions

View File

@@ -4,14 +4,16 @@ import (
"testing"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/internal/gojs/config"
"github.com/tetratelabs/wazero/internal/testing/require"
)
func Test_argsAndEnv(t *testing.T) {
t.Parallel()
stdout, stderr, err := compileAndRun(testCtx, "argsenv", wazero.NewModuleConfig().
WithEnv("c", "d").WithEnv("a", "b"))
stdout, stderr, err := compileAndRun(testCtx, "argsenv", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
return moduleConfig.WithEnv("c", "d").WithEnv("a", "b"), config.NewConfig()
})
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.Zero(t, stderr)

View File

@@ -26,7 +26,7 @@ func newJsGlobal(rt http.RoundTripper) *jsVal {
"fs": jsfs,
"Date": jsDateConstructor,
}).
addFunction("fetch", httpFetch{})
addFunction("fetch", &httpFetch{rt})
}
var (

View File

@@ -20,13 +20,20 @@ import (
"github.com/tetratelabs/wazero/experimental/gojs"
"github.com/tetratelabs/wazero/internal/fstest"
internalgojs "github.com/tetratelabs/wazero/internal/gojs"
"github.com/tetratelabs/wazero/internal/gojs/config"
"github.com/tetratelabs/wazero/internal/gojs/run"
"github.com/tetratelabs/wazero/internal/testing/binaryencoding"
"github.com/tetratelabs/wazero/internal/wasm"
binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary"
)
func compileAndRun(ctx context.Context, arg string, config wazero.ModuleConfig) (stdout, stderr string, err error) {
type newConfig func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config)
func defaultConfig(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
return moduleConfig, config.NewConfig()
}
func compileAndRun(ctx context.Context, arg string, config newConfig) (stdout, stderr string, err error) {
rt := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig().
// https://github.com/tetratelabs/wazero/issues/992
WithMemoryCapacityFromMax(true).
@@ -34,7 +41,7 @@ func compileAndRun(ctx context.Context, arg string, config wazero.ModuleConfig)
return compileAndRunWithRuntime(ctx, rt, arg, config) // use global runtime
}
func compileAndRunWithRuntime(ctx context.Context, r wazero.Runtime, arg string, config wazero.ModuleConfig) (stdout, stderr string, err error) {
func compileAndRunWithRuntime(ctx context.Context, r wazero.Runtime, arg string, config newConfig) (stdout, stderr string, err error) {
var stdoutBuf, stderrBuf bytes.Buffer
builder := r.NewHostModuleBuilder("go")
@@ -49,13 +56,15 @@ func compileAndRunWithRuntime(ctx context.Context, r wazero.Runtime, arg string,
log.Panicln(err)
}
var s *internalgojs.State
s, err = run.RunAndReturnState(ctx, r, compiled, config.
mc, c := config(wazero.NewModuleConfig().
WithStdout(&stdoutBuf).
WithStderr(&stderrBuf).
WithArgs("test", arg))
var s *internalgojs.State
s, err = run.RunAndReturnState(ctx, r, compiled, mc, c)
if err == nil {
if !reflect.DeepEqual(s, internalgojs.NewState(ctx)) {
if !reflect.DeepEqual(s, internalgojs.NewState(c)) {
log.Panicf("unexpected state: %v\n", s)
}
}

View File

@@ -0,0 +1,42 @@
// Package config exists to avoid dependency cycles when keeping most of gojs
// code internal.
package config
import (
"net/http"
"os"
"path/filepath"
"github.com/tetratelabs/wazero/internal/platform"
)
type Config struct {
OsWorkdir bool
Rt http.RoundTripper
// Workdir is the actual working directory value.
Workdir string
}
func NewConfig() *Config {
return &Config{Workdir: "/"}
}
func (c *Config) Clone() *Config {
ret := *c // copy except maps which share a ref
return &ret
}
func (c *Config) Init() error {
if c.OsWorkdir {
workdir, err := os.Getwd()
if err != nil {
return err
}
// Ensure if used on windows, the input path is translated to a POSIX one.
workdir = platform.ToPosixPath(workdir)
// Strip the volume of the path, for example C:\
c.Workdir = workdir[len(filepath.VolumeName(workdir)):]
}
return nil
}

View File

@@ -0,0 +1,22 @@
package config
import (
"strings"
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
)
func TestConfig_Init(t *testing.T) {
t.Parallel()
t.Run("OsWorkdir", func(t *testing.T) {
c := &Config{OsWorkdir: true}
require.NoError(t, c.Init())
actual := c.Workdir
// Check c:\ or d:\ aren't retained.
require.Equal(t, -1, strings.IndexByte(actual, '\\'))
require.Equal(t, -1, strings.IndexByte(actual, ':'))
})
}

View File

@@ -5,7 +5,6 @@ import (
"context"
"testing"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/experimental/logging"
"github.com/tetratelabs/wazero/internal/testing/require"
@@ -18,7 +17,7 @@ func Test_crypto(t *testing.T) {
loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{},
logging.NewHostLoggingListenerFactory(&log, logging.LogScopeRandom))
stdout, stderr, err := compileAndRun(loggingCtx, "crypto", wazero.NewModuleConfig())
stdout, stderr, err := compileAndRun(loggingCtx, "crypto", defaultConfig)
require.Zero(t, stderr)
require.EqualError(t, err, `module "" closed with exit_code(0)`)

View File

@@ -539,8 +539,8 @@ type jsfsChown struct{}
func (jsfsChown) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := resolvePath(ctx, args[0].(string))
uid := goos.ValueToUint32(args[1])
gid := goos.ValueToUint32(args[2])
uid := goos.ValueToInt32(args[1])
gid := goos.ValueToInt32(args[2])
callback := args[3].(funcWrapper)
fsc := mod.(*wasm.CallContext).Sys.FS()

View File

@@ -7,6 +7,7 @@ import (
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/internal/fstest"
"github.com/tetratelabs/wazero/internal/gojs/config"
"github.com/tetratelabs/wazero/internal/platform"
"github.com/tetratelabs/wazero/internal/testing/require"
)
@@ -14,7 +15,9 @@ import (
func Test_fs(t *testing.T) {
t.Parallel()
stdout, stderr, err := compileAndRun(testCtx, "fs", wazero.NewModuleConfig().WithFS(testFS))
stdout, stderr, err := compileAndRun(testCtx, "fs", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
return defaultConfig(moduleConfig.WithFS(testFS))
})
require.Zero(t, stderr)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
@@ -44,7 +47,9 @@ func Test_testfs(t *testing.T) {
require.NoError(t, fstest.WriteTestFiles(testfsDir))
fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
stdout, stderr, err := compileAndRun(testCtx, "testfs", wazero.NewModuleConfig().WithFSConfig(fsConfig))
stdout, stderr, err := compileAndRun(testCtx, "testfs", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
return defaultConfig(moduleConfig.WithFSConfig(fsConfig))
})
require.Zero(t, stderr)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
@@ -59,7 +64,9 @@ func Test_writefs(t *testing.T) {
// test expects to write under /tmp
require.NoError(t, os.Mkdir(path.Join(tmpDir, "tmp"), 0o700))
stdout, stderr, err := compileAndRun(testCtx, "writefs", wazero.NewModuleConfig().WithFSConfig(fsConfig))
stdout, stderr, err := compileAndRun(testCtx, "writefs", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
return defaultConfig(moduleConfig.WithFSConfig(fsConfig))
})
require.Zero(t, stderr)
require.EqualError(t, err, `module "" closed with exit_code(0)`)

View File

@@ -265,6 +265,15 @@ func ValueToUint32(arg interface{}) uint32 {
return uint32(arg.(float64))
}
func ValueToInt32(arg interface{}) int32 {
if arg == RefValueZero || arg == Undefined {
return 0
} else if u, ok := arg.(int); ok {
return int32(u)
}
return int32(uint32(arg.(float64)))
}
// GetFunction allows getting a JavaScript property by name.
type GetFunction interface {
Get(ctx context.Context, propertyKey string) interface{}

View File

@@ -15,15 +15,6 @@ import (
// headersConstructor = Get("Headers").New() // http.Roundtrip && "fetch"
var headersConstructor = newJsVal(goos.RefHttpHeadersConstructor, "Headers")
type RoundTripperKey struct{}
func getRoundTripper(ctx context.Context) http.RoundTripper {
if rt, ok := ctx.Value(RoundTripperKey{}).(http.RoundTripper); ok {
return rt
}
return nil
}
// httpFetch implements jsFn for http.RoundTripper
//
// Reference in roundtrip_js.go init
@@ -33,10 +24,10 @@ func getRoundTripper(ctx context.Context) http.RoundTripper {
// In http.Transport RoundTrip, this returns a promise
//
// fetchPromise := js.Global().Call("fetch", req.URL.String(), opt)
type httpFetch struct{}
type httpFetch struct{ rt http.RoundTripper }
func (httpFetch) invoke(ctx context.Context, _ api.Module, args ...interface{}) (interface{}, error) {
rt := getRoundTripper(ctx)
func (h *httpFetch) invoke(ctx context.Context, _ api.Module, args ...interface{}) (interface{}, error) {
rt := h.rt
if rt == nil {
panic("unexpected to reach here without roundtripper as property is nil checked")
}

View File

@@ -8,7 +8,7 @@ import (
"testing"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/experimental/gojs"
"github.com/tetratelabs/wazero/internal/gojs/config"
"github.com/tetratelabs/wazero/internal/testing/require"
)
@@ -21,7 +21,7 @@ func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
func Test_http(t *testing.T) {
t.Parallel()
ctx := gojs.WithRoundTripper(testCtx, roundTripperFunc(func(req *http.Request) (*http.Response, error) {
rt := roundTripperFunc(func(req *http.Request) (*http.Response, error) {
if req.URL.Path == "/error" {
return nil, errors.New("error")
}
@@ -38,10 +38,13 @@ func Test_http(t *testing.T) {
Body: io.NopCloser(strings.NewReader("abcdef")),
ContentLength: 6,
}, nil
}))
})
stdout, stderr, err := compileAndRun(ctx, "http", wazero.NewModuleConfig().
WithEnv("BASE_URL", "http://host"))
stdout, stderr, err := compileAndRun(testCtx, "http", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
return moduleConfig.WithEnv("BASE_URL", "http://host"), &config.Config{
Rt: rt,
}
})
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.Zero(t, stderr)

View File

@@ -301,6 +301,11 @@ func writeVal(w logging.Writer, name string, val interface{}) {
return
}
switch name {
case "uid", "gid":
w.WriteString(name) //nolint
w.WriteByte('=') //nolint
id := int(goos.ValueToInt32(val))
w.WriteString(strconv.Itoa(id)) //nolint
case "mask", "mode", "oldmask", "perm":
w.WriteString(name) //nolint
w.WriteByte('=') //nolint

View File

@@ -10,6 +10,7 @@ import (
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/experimental/logging"
"github.com/tetratelabs/wazero/internal/gojs/config"
"github.com/tetratelabs/wazero/internal/testing/require"
)
@@ -20,7 +21,7 @@ func Test_exit(t *testing.T) {
loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{},
logging.NewHostLoggingListenerFactory(&log, logging.LogScopeProc))
stdout, stderr, err := compileAndRun(loggingCtx, "exit", wazero.NewModuleConfig())
stdout, stderr, err := compileAndRun(loggingCtx, "exit", defaultConfig)
require.EqualError(t, err, `module "" closed with exit_code(255)`)
require.Zero(t, stderr)
@@ -33,7 +34,7 @@ func Test_exit(t *testing.T) {
func Test_goroutine(t *testing.T) {
t.Parallel()
stdout, stderr, err := compileAndRun(testCtx, "goroutine", wazero.NewModuleConfig())
stdout, stderr, err := compileAndRun(testCtx, "goroutine", defaultConfig)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.Zero(t, stderr)
@@ -49,7 +50,7 @@ func Test_mem(t *testing.T) {
loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{},
logging.NewHostLoggingListenerFactory(&log, logging.LogScopeMemory))
stdout, stderr, err := compileAndRun(loggingCtx, "mem", wazero.NewModuleConfig())
stdout, stderr, err := compileAndRun(loggingCtx, "mem", defaultConfig)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.Zero(t, stderr)
@@ -65,8 +66,9 @@ func Test_stdio(t *testing.T) {
t.Parallel()
input := "stdin\n"
stdout, stderr, err := compileAndRun(testCtx, "stdio", wazero.NewModuleConfig().
WithStdin(strings.NewReader(input)))
stdout, stderr, err := compileAndRun(testCtx, "stdio", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
return defaultConfig(moduleConfig.WithStdin(strings.NewReader(input)))
})
require.Equal(t, "stderr 6\n", stderr)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
@@ -83,8 +85,9 @@ func Test_stdio_large(t *testing.T) {
size := 2 * 1024 * 1024 // 2MB
input := make([]byte, size)
stdout, stderr, err := compileAndRun(loggingCtx, "stdio", wazero.NewModuleConfig().
WithStdin(bytes.NewReader(input)))
stdout, stderr, err := compileAndRun(loggingCtx, "stdio", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
return defaultConfig(moduleConfig.WithStdin(bytes.NewReader(input)))
})
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.Equal(t, fmt.Sprintf("stderr %d\n", size), stderr)
@@ -102,7 +105,7 @@ func Test_stdio_large(t *testing.T) {
func Test_gc(t *testing.T) {
t.Parallel()
stdout, stderr, err := compileAndRun(testCtx, "gc", wazero.NewModuleConfig())
stdout, stderr, err := compileAndRun(testCtx, "gc", defaultConfig)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.Equal(t, "", stderr)

View File

@@ -7,25 +7,35 @@ import (
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/internal/gojs"
"github.com/tetratelabs/wazero/internal/gojs/config"
)
func RunAndReturnState(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule, config wazero.ModuleConfig) (*gojs.State, error) {
// Instantiate the module compiled by go, noting it has no init function.
func RunAndReturnState(
ctx context.Context,
r wazero.Runtime,
compiled wazero.CompiledModule,
moduleConfig wazero.ModuleConfig,
config *config.Config,
) (*gojs.State, error) {
if err := config.Init(); err != nil {
return nil, err
}
mod, err := r.InstantiateModule(ctx, compiled, config)
// Instantiate the module compiled by go, noting it has no init function.
mod, err := r.InstantiateModule(ctx, compiled, moduleConfig)
if err != nil {
return nil, err
}
defer mod.Close(ctx)
// Extract the args and env from the module config and write it to memory.
// Extract the args and env from the module Config and write it to memory.
argc, argv, err := gojs.WriteArgsAndEnviron(mod)
if err != nil {
return nil, err
}
// Create host-side state for JavaScript values and events.
s := gojs.NewState(ctx)
s := gojs.NewState(config)
ctx = context.WithValue(ctx, gojs.StateKey{}, s)
// Invoke the run function.

View File

@@ -6,24 +6,16 @@ import (
"math"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/gojs/config"
"github.com/tetratelabs/wazero/internal/gojs/goos"
"github.com/tetratelabs/wazero/internal/gojs/values"
)
type WorkdirKey struct{}
func getWorkdir(ctx context.Context) string {
if wd, ok := ctx.Value(WorkdirKey{}).(string); ok {
return wd
}
return "/"
}
func NewState(ctx context.Context) *State {
func NewState(config *config.Config) *State {
return &State{
values: values.NewValues(),
valueGlobal: newJsGlobal(getRoundTripper(ctx)),
cwd: getWorkdir(ctx),
valueGlobal: newJsGlobal(config.Rt),
cwd: config.Workdir,
umask: 0o0022,
_nextCallbackTimeoutID: 1,
_scheduledTimeouts: map[uint32]chan bool{},

View File

@@ -3,14 +3,13 @@ package gojs_test
import (
"testing"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/internal/testing/require"
)
func Test_syscall(t *testing.T) {
t.Parallel()
stdout, stderr, err := compileAndRun(testCtx, "syscall", wazero.NewModuleConfig())
stdout, stderr, err := compileAndRun(testCtx, "syscall", defaultConfig)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.Zero(t, stderr)

View File

@@ -5,7 +5,6 @@ import (
"context"
"testing"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/experimental/logging"
"github.com/tetratelabs/wazero/internal/testing/require"
@@ -18,7 +17,7 @@ func Test_time(t *testing.T) {
loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{},
logging.NewHostLoggingListenerFactory(&log, logging.LogScopeClock))
stdout, stderr, err := compileAndRun(loggingCtx, "time", wazero.NewModuleConfig())
stdout, stderr, err := compileAndRun(loggingCtx, "time", defaultConfig)
require.EqualError(t, err, `module "" closed with exit_code(0)`)
require.Zero(t, stderr)