Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
mstoykov committed Mar 28, 2023
1 parent 4f5c142 commit bae7d2b
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 185 deletions.
79 changes: 38 additions & 41 deletions js/bundle.go
Expand Up @@ -18,6 +18,7 @@ import (
"go.k6.io/k6/js/common"
"go.k6.io/k6/js/compiler"
"go.k6.io/k6/js/eventloop"
"go.k6.io/k6/js/modules"
"go.k6.io/k6/lib"
"go.k6.io/k6/lib/consts"
"go.k6.io/k6/loader"
Expand All @@ -36,7 +37,7 @@ type Bundle struct {
pwd *url.URL

callableExports map[string]struct{}
moduleResolver *moduleResolver
ModuleResolver *modules.ModuleResolver
}

// A BundleInstance is a self-contained instance of a Bundle.
Expand All @@ -46,18 +47,18 @@ type BundleInstance struct {
// TODO: maybe just have a reference to the Bundle? or save and pass rtOpts?
env map[string]string

mainModuleInstance moduleInstance
moduleVUImpl *moduleVUImpl
mainModuleExports *goja.Object
moduleVUImpl *moduleVUImpl
}

func (bi *BundleInstance) getCallableExport(name string) goja.Callable {
fn, ok := goja.AssertFunction(bi.mainModuleInstance.exports().Get(name))
fn, ok := goja.AssertFunction(bi.getExported(name))
_ = ok // TODO maybe return it
return fn
}

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

// NewBundle creates a new bundle from a source file and a filesystem.
Expand Down Expand Up @@ -87,22 +88,22 @@ func newBundle(
preInitState: piState,
}
c := bundle.newCompiler(piState.Logger)
bundle.moduleResolver = newModuleResolution(getJSModules(), generateCJSLoad(bundle, c))
bundle.ModuleResolver = modules.NewModuleResolver(getJSModules(), generateCJSLoad(bundle, c))

if err = bundle.moduleResolver.setMain(src, c); err != nil {
if err = bundle.ModuleResolver.SetMain(src, c); err != nil {
return nil, err
}
// 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.
// TODO use a real context
vuImpl := &moduleVUImpl{ctx: context.Background(), runtime: goja.New()}
vuImpl.eventLoop = eventloop.New(vuImpl)
instance, err := bundle.instantiate(vuImpl, 0)
exports, err := bundle.instantiate(vuImpl, 0)
if err != nil {
return nil, err
}

err = bundle.populateExports(piState.Logger, updateOptions, instance)
err = bundle.populateExports(updateOptions, exports)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -159,11 +160,7 @@ func (b *Bundle) makeArchive() *lib.Archive {
}

// populateExports validates and extracts exported objects
func (b *Bundle) populateExports(logger logrus.FieldLogger, updateOptions bool, instance moduleInstance) error {
exports := instance.exports()
if exports == nil {
return errors.New("exports must be an object")
}
func (b *Bundle) populateExports(updateOptions bool, exports *goja.Object) error {
for _, k := range exports.Keys() {
v := exports.Get(k)
if _, ok := goja.AssertFunction(v); ok && k != consts.Options {
Expand All @@ -185,7 +182,7 @@ func (b *Bundle) populateExports(logger logrus.FieldLogger, updateOptions bool,
if uerr := json.Unmarshal(data, &b.Options); uerr != nil {
return uerr
}
logger.WithError(err).Warn("There were unknown fields in the options exported in the script")
b.preInitState.Logger.WithError(err).Warn("There were unknown fields in the options exported in the script")
}
case consts.SetupFn:
return errors.New("exported 'setup' must be a function")
Expand All @@ -207,25 +204,23 @@ func (b *Bundle) Instantiate(ctx context.Context, vuID uint64) (*BundleInstance,
// runtime, but no state, to allow module-provided types to function within the init context.
vuImpl := &moduleVUImpl{ctx: ctx, runtime: goja.New()}
vuImpl.eventLoop = eventloop.New(vuImpl)
instance, err := b.instantiate(vuImpl, vuID)
exports, err := b.instantiate(vuImpl, vuID)
if err != nil {
return nil, err
}

bi := &BundleInstance{
Runtime: vuImpl.runtime,
env: b.preInitState.RuntimeOptions.Env,
moduleVUImpl: vuImpl,
mainModuleInstance: instance,
Runtime: vuImpl.runtime,
env: b.preInitState.RuntimeOptions.Env,
moduleVUImpl: vuImpl,
mainModuleExports: exports,
}

// Grab any exported functions that could be executed. These were
// already pre-validated in cmd.validateScenarioConfig(), just get them here.
exports := instance.exports()

jsOptions := exports.Get("options")
var jsOptionsObj *goja.Object
if jsOptions == nil || goja.IsNull(jsOptions) || goja.IsUndefined(jsOptions) {
if common.IsNullish(jsOptions) {
jsOptionsObj = vuImpl.runtime.NewObject()
err := exports.Set("options", jsOptionsObj)
if err != nil {
Expand Down Expand Up @@ -255,7 +250,7 @@ func (b *Bundle) newCompiler(logger logrus.FieldLogger) *compiler.Compiler {
return c
}

func (b *Bundle) instantiate(vuImpl *moduleVUImpl, vuID uint64) (moduleInstance, error) {
func (b *Bundle) instantiate(vuImpl *moduleVUImpl, vuID uint64) (*goja.Object, error) {
rt := vuImpl.runtime
err := b.setupJSRuntime(rt, int64(vuID), b.preInitState.Logger)
if err != nil {
Expand All @@ -270,8 +265,8 @@ func (b *Bundle) instantiate(vuImpl *moduleVUImpl, vuID uint64) (moduleInstance,
LookupEnv: b.preInitState.LookupEnv,
}

modSys := newModuleSystem(b.moduleResolver, vuImpl)
unbindInit := b.setInitGlobals(rt, modSys)
modSys := modules.NewModuleSystem(b.ModuleResolver, vuImpl)
unbindInit := b.setInitGlobals(rt, vuImpl, modSys)
vuImpl.initEnv = initenv
defer func() {
unbindInit()
Expand All @@ -290,16 +285,13 @@ func (b *Bundle) instantiate(vuImpl *moduleVUImpl, vuID uint64) (moduleInstance,
close(initDone)
}()

var instance moduleInstance
var exportsV goja.Value
err = common.RunWithPanicCatching(b.preInitState.Logger, rt, func() error {
return vuImpl.eventLoop.Start(func() error {
//nolint:shadow,govet // here we shadow err on purpose
mod, err := b.moduleResolver.resolve(b.pwd, b.sourceData.URL.String())
if err != nil {
return err // TODO wrap as this should never happen
}
instance = mod.Instantiate(vuImpl)
return instance.execute()
var err error
exportsV, err = modSys.RunMain()
return err
})
})

Expand All @@ -312,7 +304,12 @@ func (b *Bundle) instantiate(vuImpl *moduleVUImpl, vuID uint64) (moduleInstance,
}
return nil, err
}
if exports := instance.exports(); exports == nil {
if common.IsNullish(exportsV) {
return nil, errors.New("exports must be not nil")
}
exports := exportsV.ToObject(vuImpl.runtime)

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

Expand All @@ -324,7 +321,7 @@ func (b *Bundle) instantiate(vuImpl *moduleVUImpl, vuID uint64) (moduleInstance,

rt.SetRandSource(common.NewRandSource())

return instance, nil
return exports, nil
}

func (b *Bundle) setupJSRuntime(rt *goja.Runtime, vuID int64, logger logrus.FieldLogger) error {
Expand Down Expand Up @@ -357,21 +354,21 @@ func (b *Bundle) setupJSRuntime(rt *goja.Runtime, vuID int64, logger logrus.Fiel
return nil
}

func (b *Bundle) setInitGlobals(rt *goja.Runtime, modSys *moduleSystem) (unset func()) {
func (b *Bundle) setInitGlobals(rt *goja.Runtime, vu *moduleVUImpl, modSys *modules.ModuleSystem) (unset func()) {
mustSet := func(k string, v interface{}) {
if err := rt.Set(k, v); err != nil {
panic(fmt.Errorf("failed to set '%s' global object: %w", k, err))
}
}
r := requireImpl{
vu: modSys.vu,
vu: vu,
modules: modSys,
pwd: b.pwd,
}
mustSet("require", r.require)

mustSet("open", func(filename string, args ...string) (goja.Value, error) {
if modSys.vu.State() != nil { // fix
if vu.state != nil { // fix
return nil, fmt.Errorf(cantBeUsedOutsideInitContextMsg, "open")
}

Expand All @@ -387,8 +384,8 @@ func (b *Bundle) setInitGlobals(rt *goja.Runtime, modSys *moduleSystem) (unset f
}
}

func generateCJSLoad(b *Bundle, c *compiler.Compiler) cjsModuleLoader {
return func(specifier *url.URL, name string) (*cjsModule, error) {
func generateCJSLoad(b *Bundle, c *compiler.Compiler) modules.CJSModuleLoader {
return func(specifier *url.URL, name string) (*modules.CJSModule, error) {
if filepath.IsAbs(name) && runtime.GOOS == "windows" {
b.preInitState.Logger.Warnf("'%s' was imported with an absolute path - this won't be cross-platform and "+
"won't work if you move the script between machines or run it with `k6 cloud`; if absolute paths are "+
Expand All @@ -399,6 +396,6 @@ func generateCJSLoad(b *Bundle, c *compiler.Compiler) cjsModuleLoader {
if err != nil {
return nil, err
}
return cjsmoduleFromString(specifier, d.Data, c)
return modules.CJSModuleFromString(specifier, d.Data, c)
}
}
2 changes: 1 addition & 1 deletion js/initcontext.go
Expand Up @@ -82,7 +82,7 @@ func allowOnlyOpenedFiles(fs afero.Fs) {

type requireImpl struct {
vu modules.VU
modules *moduleSystem
modules *modules.ModuleSystem
pwd *url.URL
}

Expand Down
121 changes: 0 additions & 121 deletions js/modules.go

This file was deleted.

0 comments on commit bae7d2b

Please sign in to comment.