Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native ESM support #3456

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 11 additions & 11 deletions cmd/tests/cmd_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -944,8 +944,8 @@ func TestAbortedByScriptSetupErrorWithDependency(t *testing.T) {
if runtime.GOOS == "windows" {
rootPath += "c:/"
}
assert.Contains(t, stdout, `level=error msg="Error: baz\n\tat baz (`+rootPath+`test/bar.js:6:9(3))\n\tat `+
rootPath+`test/bar.js:3:3(3)\n\tat setup (`+rootPath+`test/test.js:5:3(9))\n" hint="script exception"`)
assert.Contains(t, stdout, `level=error msg="Error: baz\n\tat baz (`+rootPath+`test/bar.js:6:10(3))\n\tat default (`+
rootPath+`test/bar.js:3:7(3))\n\tat setup (`+rootPath+`test/test.js:5:7(8))\n" hint="script exception"`)
assert.Contains(t, stdout, `level=debug msg="Sending test finished" output=cloud ref=123 run_status=7 tainted=false`)
assert.Contains(t, stdout, "bogus summary")
}
Expand Down Expand Up @@ -2106,10 +2106,10 @@ func TestEventSystemError(t *testing.T) {
"got event Init with data '<nil>'",
"got event TestStart with data '<nil>'",
"got event IterStart with data '{Iteration:0 VUID:1 ScenarioName:default Error:<nil>}'",
"got event IterEnd with data '{Iteration:0 VUID:1 ScenarioName:default Error:test aborted: oops! at file:///-:11:16(6)}'",
"got event IterEnd with data '{Iteration:0 VUID:1 ScenarioName:default Error:test aborted: oops! at default (file:///-:11:16(5))}'",
"got event TestEnd with data '<nil>'",
"got event Exit with data '&{Error:test aborted: oops! at file:///-:11:16(6)}'",
"test aborted: oops! at file:///-:11:16(6)",
"got event Exit with data '&{Error:test aborted: oops! at default (file:///-:11:16(5))}'",
"test aborted: oops! at default (file:///-:11:16(5))",
},
expExitCode: exitcodes.ScriptAborted,
},
Expand All @@ -2118,8 +2118,8 @@ func TestEventSystemError(t *testing.T) {
script: "undefinedVar",
expLog: []string{
"got event Exit with data '&{Error:could not initialize '-': could not load JS test " +
"'file:///-': ReferenceError: undefinedVar is not defined\n\tat file:///-:2:0(12)\n}'",
"ReferenceError: undefinedVar is not defined\n\tat file:///-:2:0(12)\n",
"'file:///-': ReferenceError: undefinedVar is not defined\n\tat file:///-:2:1(8)\n}'",
"ReferenceError: undefinedVar is not defined\n\tat file:///-:2:1(8)\n",
},
expExitCode: exitcodes.ScriptException,
},
Expand All @@ -2138,11 +2138,11 @@ func TestEventSystemError(t *testing.T) {
"got event Init with data '<nil>'",
"got event TestStart with data '<nil>'",
"got event IterStart with data '{Iteration:0 VUID:1 ScenarioName:default Error:<nil>}'",
"got event IterEnd with data '{Iteration:0 VUID:1 ScenarioName:default Error:Error: oops!\n\tat file:///-:9:11(3)\n}'",
"Error: oops!\n\tat file:///-:9:11(3)\n",
"got event IterEnd with data '{Iteration:0 VUID:1 ScenarioName:default Error:Error: oops!\n\tat default (file:///-:9:12(3))\n}'",
"Error: oops!\n\tat default (file:///-:9:12(3))\n",
"got event IterStart with data '{Iteration:1 VUID:1 ScenarioName:default Error:<nil>}'",
"got event IterEnd with data '{Iteration:1 VUID:1 ScenarioName:default Error:Error: oops!\n\tat file:///-:9:11(3)\n}'",
"Error: oops!\n\tat file:///-:9:11(3)\n",
"got event IterEnd with data '{Iteration:1 VUID:1 ScenarioName:default Error:Error: oops!\n\tat default (file:///-:9:12(3))\n}'",
"Error: oops!\n\tat default (file:///-:9:12(3))\n",
"got event TestEnd with data '<nil>'",
"got event Exit with data '&{Error:<nil>}'",
},
Expand Down
2 changes: 1 addition & 1 deletion cmd/tests/eventloop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func TestEventLoopDoesntCrossIterations(t *testing.T) {
eventLoopTest(t, script, func(logLines []string) {
require.Equal(t, []string{
"setTimeout 1 was stopped because the VU iteration was interrupted",
"just error\n\tat file:///-:13:4(15)\n", "1",
"just error\n\tat default (file:///-:13:5(14))\n", "1",
}, logLines)
})
}
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,5 @@
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
)

replace github.com/dop251/goja => github.com/mstoykov/goja v0.0.0-20231212144616-08f562ee86d0

Check failure on line 102 in go.mod

View workflow job for this annotation

GitHub Actions / lint

replacement are not allowed: github.com/dop251/goja (gomoddirectives)
9 changes: 2 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI=
github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja v0.0.0-20240220182346-e401ed450204 h1:O7I1iuzEA7SG+dK8ocOBSlYAA9jBUmCYl/Qa7ey7JAM=
github.com/dop251/goja v0.0.0-20240220182346-e401ed450204/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
Expand Down Expand Up @@ -124,7 +119,6 @@ github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
Expand All @@ -146,6 +140,8 @@ github.com/mstoykov/atlas v0.0.0-20220811071828-388f114305dd h1:AC3N94irbx2kWGA8
github.com/mstoykov/atlas v0.0.0-20220811071828-388f114305dd/go.mod h1:9vRHVuLCjoFfE3GT06X0spdOAO+Zzo4AMjdIwUHBvAk=
github.com/mstoykov/envconfig v1.4.1-0.20220114105314-765c6d8c76f1 h1:94EkGmhXrVUEal+uLwFUf4fMXPhZpM5tYxuIsxrCCbI=
github.com/mstoykov/envconfig v1.4.1-0.20220114105314-765c6d8c76f1/go.mod h1:vk/d9jpexY2Z9Bb0uB4Ndesss1Sr0Z9ZiGUrg5o9VGk=
github.com/mstoykov/goja v0.0.0-20231212144616-08f562ee86d0 h1:AcJZgDvroNJdSX/Ip5hN0P5xhatMwmJBbLHqn3jqjME=
github.com/mstoykov/goja v0.0.0-20231212144616-08f562ee86d0/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
github.com/mstoykov/k6-taskqueue-lib v0.1.0 h1:M3eww1HSOLEN6rIkbNOJHhOVhlqnqkhYj7GTieiMBz4=
github.com/mstoykov/k6-taskqueue-lib v0.1.0/go.mod h1:PXdINulapvmzF545Auw++SCD69942FeNvUztaa9dVe4=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
Expand Down Expand Up @@ -343,7 +339,6 @@ gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UD
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/guregu/null.v3 v3.3.0 h1:8j3ggqq+NgKt/O7mbFVUFKUMWN+l1AmT5jQmJ6nPh2c=
gopkg.in/guregu/null.v3 v3.3.0/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y=
Expand Down
80 changes: 38 additions & 42 deletions js/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ type BundleInstance struct {
// TODO: maybe just have a reference to the Bundle? or save and pass rtOpts?
env map[string]string

mainModuleExports *goja.Object
moduleVUImpl *moduleVUImpl
mainModule goja.ModuleRecord
mainModuleInstance goja.ModuleInstance
moduleVUImpl *moduleVUImpl
}

func (bi *BundleInstance) getCallableExport(name string) goja.Callable {
Expand All @@ -59,7 +60,7 @@ func (bi *BundleInstance) getCallableExport(name string) goja.Callable {
}

func (bi *BundleInstance) getExported(name string) goja.Value {
return bi.mainModuleExports.Get(name)
return bi.mainModuleInstance.GetBindingValue(name)
}

// NewBundle creates a new bundle from a source file and a filesystem.
Expand Down Expand Up @@ -89,7 +90,7 @@ func newBundle(
preInitState: piState,
}
c := bundle.newCompiler(piState.Logger)
bundle.ModuleResolver = modules.NewModuleResolver(getJSModules(), generateFileLoad(bundle), c)
bundle.ModuleResolver = modules.NewModuleResolver(getJSModules(), generateFileLoad(bundle), c, src.URL.JoinPath(".."))

// Instantiate the bundle into a new VM using a bound init context. This uses a context with a
// runtime, but no state, to allow module-provided types to function within the init context.
Expand All @@ -103,12 +104,12 @@ func newBundle(
},
}
vuImpl.eventLoop = eventloop.New(vuImpl)
exports, err := bundle.instantiate(vuImpl, 0)
bi, err := bundle.instantiate(vuImpl, 0)
if err != nil {
return nil, err
}

err = bundle.populateExports(updateOptions, exports)
err = bundle.populateExports(updateOptions, bi)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -165,9 +166,9 @@ func (b *Bundle) makeArchive() *lib.Archive {
}

// populateExports validates and extracts exported objects
func (b *Bundle) populateExports(updateOptions bool, exports *goja.Object) error {
for _, k := range exports.Keys() {
v := exports.Get(k)
func (b *Bundle) populateExports(updateOptions bool, bi *BundleInstance) error {
for _, k := range bi.mainModule.GetExportedNames() {
v := bi.getExported(k)
if _, ok := goja.AssertFunction(v); ok && k != consts.Options {
b.callableExports[k] = struct{}{}
continue
Expand Down Expand Up @@ -216,40 +217,34 @@ func (b *Bundle) Instantiate(ctx context.Context, vuID uint64) (*BundleInstance,
},
}
vuImpl.eventLoop = eventloop.New(vuImpl)
exports, err := b.instantiate(vuImpl, vuID)
bi, err := b.instantiate(vuImpl, vuID)
if err != nil {
return nil, err
}

bi := &BundleInstance{
Runtime: vuImpl.runtime,
env: b.preInitState.RuntimeOptions.Env,
moduleVUImpl: vuImpl,
mainModuleExports: exports,
if err = bi.manipulateOptions(b.Options); err != nil {
return nil, err
}

return bi, nil
}

func (bi *BundleInstance) manipulateOptions(options lib.Options) error {
// Grab any exported functions that could be executed. These were
// already pre-validated in cmd.validateScenarioConfig(), just get them here.
jsOptions := exports.Get(consts.Options)
jsOptions := bi.getExported(consts.Options)
var jsOptionsObj *goja.Object
if common.IsNullish(jsOptions) {
jsOptionsObj = vuImpl.runtime.NewObject()
err := exports.Set(consts.Options, jsOptionsObj)
if err != nil {
return nil, fmt.Errorf("couldn't set exported options with merged values: %w", err)
}
} else {
jsOptionsObj = jsOptions.ToObject(vuImpl.runtime)
return nil
}

jsOptionsObj = jsOptions.ToObject(bi.Runtime)
var instErr error
b.Options.ForEachSpecified("json", func(key string, val interface{}) {
options.ForEachSpecified("json", func(key string, val interface{}) {
if err := jsOptionsObj.Set(key, val); err != nil {
instErr = err
}
})

return bi, instErr
return instErr
}

func (b *Bundle) newCompiler(logger logrus.FieldLogger) *compiler.Compiler {
Expand All @@ -262,7 +257,7 @@ func (b *Bundle) newCompiler(logger logrus.FieldLogger) *compiler.Compiler {
return c
}

func (b *Bundle) instantiate(vuImpl *moduleVUImpl, vuID uint64) (*goja.Object, error) {
func (b *Bundle) instantiate(vuImpl *moduleVUImpl, vuID uint64) (*BundleInstance, error) {
rt := vuImpl.runtime
err := b.setupJSRuntime(rt, int64(vuID), b.preInitState.Logger)
if err != nil {
Expand Down Expand Up @@ -294,14 +289,23 @@ func (b *Bundle) instantiate(vuImpl *moduleVUImpl, vuID uint64) (*goja.Object, e
}
close(initDone)
}()

var exportsV goja.Value
bi := &BundleInstance{
Runtime: vuImpl.runtime,
env: b.preInitState.RuntimeOptions.Env,
moduleVUImpl: vuImpl,
}
err = common.RunWithPanicCatching(b.preInitState.Logger, rt, func() error {
return vuImpl.eventLoop.Start(func() error {
//nolint:govet // here we shadow err on purpose
var err error
exportsV, err = modSys.RunSourceData(b.sourceData)
return err

bi.mainModule, err = modSys.RunSourceData(b.sourceData)
if err != nil {
return err
}
// TODO move this here
bi.mainModuleInstance = rt.GetModuleInstance(bi.mainModule)
return nil
})
})

Expand All @@ -314,14 +318,6 @@ func (b *Bundle) instantiate(vuImpl *moduleVUImpl, vuID uint64) (*goja.Object, e
}
return nil, err
}
if common.IsNullish(exportsV) {
return nil, errors.New("exports must not be set to null or undefined")
}
exports := exportsV.ToObject(vuImpl.runtime)

if exports == nil {
return nil, errors.New("exports must be an object")
}

// If we've already initialized the original VU init context, forbid
// any subsequent VUs to open new files
Expand All @@ -331,7 +327,7 @@ func (b *Bundle) instantiate(vuImpl *moduleVUImpl, vuID uint64) (*goja.Object, e

rt.SetRandSource(common.NewRandSource())

return exports, nil
return bi, nil
}

func (b *Bundle) setupJSRuntime(rt *goja.Runtime, vuID int64, logger logrus.FieldLogger) error {
Expand Down Expand Up @@ -402,7 +398,7 @@ func (b *Bundle) setInitGlobals(rt *goja.Runtime, vu *moduleVUImpl, modSys *modu
}
// This uses the pwd from the requireImpl
pwd := impl.internal.CurrentlyRequiredModule()
return openImpl(rt, b.filesystems["file"], &pwd, filename, args...)
return openImpl(rt, b.filesystems["file"], pwd, filename, args...)
})

return func() {
Expand Down
12 changes: 3 additions & 9 deletions js/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func TestNewBundle(t *testing.T) {
t.Parallel()
_, err := getSimpleBundle(t, "/script.js", "\x00")
require.NotNil(t, err)
require.Contains(t, err.Error(), "SyntaxError: file:///script.js: Unexpected character '\x00' (1:0)\n> 1 | \x00\n")
require.Contains(t, err.Error(), "file:///script.js: Line 1:1 Unexpected token ILLEGAL (and 1 more errors)")
})
t.Run("Error", func(t *testing.T) {
t.Parallel()
Expand Down Expand Up @@ -161,11 +161,6 @@ func TestNewBundle(t *testing.T) {
"InvalidCompat", "es1", `export default function() {};`,
`invalid compatibility mode "es1". Use: "extended", "base"`,
},
// ES2015 modules are not supported
{
"Modules", "base", `export default function() {};`,
"file:///script.js: Line 2:1 Unexpected reserved word (and 2 more errors)",
},
// BigInt is not supported
{
"BigInt", "base",
Expand Down Expand Up @@ -503,7 +498,6 @@ func TestNewBundleFromArchive(t *testing.T) {

checkArchive(t, arc, lib.RuntimeOptions{}, "") // default options
checkArchive(t, arc, extCompatModeRtOpts, "")
checkArchive(t, arc, baseCompatModeRtOpts, "Unexpected reserved word")
})

t.Run("es6_script_explicit", func(t *testing.T) {
Expand All @@ -514,7 +508,6 @@ func TestNewBundleFromArchive(t *testing.T) {

checkArchive(t, arc, lib.RuntimeOptions{}, "")
checkArchive(t, arc, extCompatModeRtOpts, "")
checkArchive(t, arc, baseCompatModeRtOpts, "Unexpected reserved word")
})

t.Run("es5_script_with_extended", func(t *testing.T) {
Expand All @@ -539,12 +532,14 @@ func TestNewBundleFromArchive(t *testing.T) {
checkArchive(t, arc, baseCompatModeRtOpts, "")
})

/* TODO remove completely - this no longer makes sense
t.Run("es6_archive_with_wrong_compat_mode", func(t *testing.T) {
t.Parallel()
arc, err := getArchive(t, es6Code, baseCompatModeRtOpts)
require.Error(t, err)
require.Nil(t, arc)
})
*/

t.Run("messed_up_archive", func(t *testing.T) {
t.Parallel()
Expand All @@ -553,7 +548,6 @@ func TestNewBundleFromArchive(t *testing.T) {
arc.CompatibilityMode = "blah" // intentionally break the archive
checkArchive(t, arc, lib.RuntimeOptions{}, "invalid compatibility mode") // fails when it uses the archive one
checkArchive(t, arc, extCompatModeRtOpts, "") // works when I force the compat mode
checkArchive(t, arc, baseCompatModeRtOpts, "Unexpected reserved word") // failes because of ES6
})

t.Run("script_options_dont_overwrite_metadata", func(t *testing.T) {
Expand Down