diff --git a/js/initcontext.go b/js/initcontext.go index 4580ce9df5d..0847e3d1fa8 100644 --- a/js/initcontext.go +++ b/js/initcontext.go @@ -151,24 +151,25 @@ func (i *InitContext) Require(arg string) goja.Value { } } -type moduleInstanceCoreImpl struct { +// TODO this likely should just be part of the initialized VU or at least to take stuff directly from it. +type moduleVUImpl struct { ctxPtr *context.Context // we can technically put lib.State here as well as anything else } -func (m *moduleInstanceCoreImpl) GetContext() context.Context { +func (m *moduleVUImpl) Context() context.Context { return *m.ctxPtr } -func (m *moduleInstanceCoreImpl) GetInitEnv() *common.InitEnvironment { +func (m *moduleVUImpl) InitEnv() *common.InitEnvironment { return common.GetInitEnv(*m.ctxPtr) // TODO thread it correctly instead } -func (m *moduleInstanceCoreImpl) GetState() *lib.State { +func (m *moduleVUImpl) State() *lib.State { return lib.GetState(*m.ctxPtr) // TODO thread it correctly instead } -func (m *moduleInstanceCoreImpl) GetRuntime() *goja.Runtime { +func (m *moduleVUImpl) Runtime() *goja.Runtime { return common.GetRuntime(*m.ctxPtr) // TODO thread it correctly instead } @@ -200,9 +201,9 @@ func (i *InitContext) requireModule(name string) (goja.Value, error) { if !ok { return nil, fmt.Errorf("unknown module: %s", name) } - if modV2, ok := mod.(modules.IsModuleV2); ok { - instance := modV2.NewModuleInstance(&moduleInstanceCoreImpl{ctxPtr: i.ctxPtr}) - return i.runtime.ToValue(toESModuleExports(instance.GetExports())), nil + if m, ok := mod.(modules.Module); ok { + instance := m.NewModuleInstance(&moduleVUImpl{ctxPtr: i.ctxPtr}) + return i.runtime.ToValue(toESModuleExports(instance.Exports())), nil } if perInstance, ok := mod.(modules.HasModuleInstancePerVU); ok { mod = perInstance.NewModuleInstancePerVU() diff --git a/js/modules/k6/execution/execution.go b/js/modules/k6/execution/execution.go index 742eb8ede0f..668a7074728 100644 --- a/js/modules/k6/execution/execution.go +++ b/js/modules/k6/execution/execution.go @@ -40,14 +40,14 @@ type ( // ModuleInstance represents an instance of the execution module. ModuleInstance struct { - modules.InstanceCore + vu modules.VU obj *goja.Object } ) var ( - _ modules.IsModuleV2 = &RootModule{} - _ modules.Instance = &ModuleInstance{} + _ modules.Module = &RootModule{} + _ modules.Instance = &ModuleInstance{} ) // New returns a pointer to a new RootModule instance. @@ -55,11 +55,11 @@ func New() *RootModule { return &RootModule{} } -// NewModuleInstance implements the modules.IsModuleV2 interface to return +// NewModuleInstance implements the modules.Module interface to return // a new instance for each VU. -func (*RootModule) NewModuleInstance(m modules.InstanceCore) modules.Instance { - mi := &ModuleInstance{InstanceCore: m} - rt := m.GetRuntime() +func (*RootModule) NewModuleInstance(vu modules.VU) modules.Instance { + mi := &ModuleInstance{vu: vu} + rt := vu.Runtime() o := rt.NewObject() defProp := func(name string, newInfo func() (*goja.Object, error)) { err := o.DefineAccessorProperty(name, rt.ToValue(func() goja.Value { @@ -82,17 +82,17 @@ func (*RootModule) NewModuleInstance(m modules.InstanceCore) modules.Instance { return mi } -// GetExports returns the exports of the execution module. -func (mi *ModuleInstance) GetExports() modules.Exports { +// Exports returns the exports of the execution module. +func (mi *ModuleInstance) Exports() modules.Exports { return modules.Exports{Default: mi.obj} } // newScenarioInfo returns a goja.Object with property accessors to retrieve // information about the scenario the current VU is running in. func (mi *ModuleInstance) newScenarioInfo() (*goja.Object, error) { - ctx := mi.GetContext() + ctx := mi.vu.Context() rt := common.GetRuntime(ctx) - vuState := mi.GetState() + vuState := mi.vu.State() if vuState == nil { return nil, errors.New("getting scenario information in the init context is not supported") } @@ -100,7 +100,7 @@ func (mi *ModuleInstance) newScenarioInfo() (*goja.Object, error) { return nil, errors.New("goja runtime is nil in context") } getScenarioState := func() *lib.ScenarioState { - ss := lib.GetScenarioState(mi.GetContext()) + ss := lib.GetScenarioState(mi.vu.Context()) if ss == nil { common.Throw(rt, errors.New("getting scenario information in the init context is not supported")) } @@ -140,7 +140,7 @@ func (mi *ModuleInstance) newScenarioInfo() (*goja.Object, error) { // newInstanceInfo returns a goja.Object with property accessors to retrieve // information about the local instance stats. func (mi *ModuleInstance) newInstanceInfo() (*goja.Object, error) { - ctx := mi.GetContext() + ctx := mi.vu.Context() es := lib.GetExecutionState(ctx) if es == nil { return nil, errors.New("getting instance information in the init context is not supported") @@ -175,7 +175,7 @@ func (mi *ModuleInstance) newInstanceInfo() (*goja.Object, error) { // newVUInfo returns a goja.Object with property accessors to retrieve // information about the currently executing VU. func (mi *ModuleInstance) newVUInfo() (*goja.Object, error) { - ctx := mi.GetContext() + ctx := mi.vu.Context() vuState := lib.GetState(ctx) if vuState == nil { return nil, errors.New("getting VU information in the init context is not supported") diff --git a/js/modules/k6/execution/execution_test.go b/js/modules/k6/execution/execution_test.go index 0d9762068d6..6827f774482 100644 --- a/js/modules/k6/execution/execution_test.go +++ b/js/modules/k6/execution/execution_test.go @@ -64,15 +64,15 @@ func setupTagsExecEnv(t *testing.T) execEnv { ctx := common.WithRuntime(context.Background(), rt) ctx = lib.WithState(ctx, state) m, ok := New().NewModuleInstance( - &modulestest.InstanceCore{ - Runtime: rt, - InitEnv: &common.InitEnvironment{}, - Ctx: ctx, - State: state, + &modulestest.VU{ + RuntimeField: rt, + InitEnvField: &common.InitEnvironment{}, + CtxField: ctx, + StateField: state, }, ).(*ModuleInstance) require.True(t, ok) - require.NoError(t, rt.Set("exec", m.GetExports().Default)) + require.NoError(t, rt.Set("exec", m.Exports().Default)) return execEnv{ Module: m, @@ -102,7 +102,7 @@ func TestVUTags(t *testing.T) { t.Parallel() tenv := setupTagsExecEnv(t) - state := tenv.Module.GetState() + state := tenv.Module.vu.State() state.Tags.Set("custom-tag", "mytag1") encoded, err := tenv.Runtime.RunString(`JSON.stringify(exec.vu.tags)`) @@ -157,7 +157,7 @@ func TestVUTags(t *testing.T) { t.Parallel() tenv := setupTagsExecEnv(t) - state := tenv.Module.GetState() + state := tenv.Module.vu.State() state.Options.Throw = null.BoolFrom(true) require.NotNil(t, state) diff --git a/js/modules/k6/metrics/metrics.go b/js/modules/k6/metrics/metrics.go index f6a26d07629..ae50bb2a367 100644 --- a/js/modules/k6/metrics/metrics.go +++ b/js/modules/k6/metrics/metrics.go @@ -37,18 +37,18 @@ import ( type Metric struct { metric *stats.Metric - core modules.InstanceCore + vu modules.VU } // ErrMetricsAddInInitContext is error returned when adding to metric is done in the init context var ErrMetricsAddInInitContext = common.NewInitContextError("Adding to metrics in the init context is not supported") func (mi *ModuleInstance) newMetric(call goja.ConstructorCall, t stats.MetricType) (*goja.Object, error) { - initEnv := mi.GetInitEnv() + initEnv := mi.vu.InitEnv() if initEnv == nil { return nil, errors.New("metrics must be declared in the init context") } - rt := mi.GetRuntime() + rt := mi.vu.Runtime() c, _ := goja.AssertFunction(rt.ToValue(func(name string, isTime ...bool) (*goja.Object, error) { valueType := stats.Default if len(isTime) > 0 && isTime[0] { @@ -58,7 +58,7 @@ func (mi *ModuleInstance) newMetric(call goja.ConstructorCall, t stats.MetricTyp if err != nil { return nil, err } - metric := &Metric{metric: m, core: mi.InstanceCore} + metric := &Metric{metric: m, vu: mi.vu} o := rt.NewObject() err = o.DefineDataProperty("name", rt.ToValue(name), goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_TRUE) if err != nil { @@ -93,7 +93,7 @@ func limitValue(v string) string { } func (m Metric) add(v goja.Value, addTags ...map[string]string) (bool, error) { - state := m.core.GetState() + state := m.vu.State() if state == nil { return false, ErrMetricsAddInInitContext } @@ -130,7 +130,7 @@ func (m Metric) add(v goja.Value, addTags ...map[string]string) (bool, error) { } sample := stats.Sample{Time: time.Now(), Metric: m.metric, Value: vfloat, Tags: stats.IntoSampleTags(&tags)} - stats.PushIfNotDone(m.core.GetContext(), state.Samples, sample) + stats.PushIfNotDone(m.vu.Context(), state.Samples, sample) return true, nil } @@ -139,18 +139,18 @@ type ( RootModule struct{} // ModuleInstance represents an instance of the metrics module ModuleInstance struct { - modules.InstanceCore + vu modules.VU } ) var ( - _ modules.IsModuleV2 = &RootModule{} - _ modules.Instance = &ModuleInstance{} + _ modules.Module = &RootModule{} + _ modules.Instance = &ModuleInstance{} ) -// NewModuleInstance implements modules.IsModuleV2 interface -func (*RootModule) NewModuleInstance(m modules.InstanceCore) modules.Instance { - return &ModuleInstance{InstanceCore: m} +// NewModuleInstance implements modules.Module interface +func (*RootModule) NewModuleInstance(m modules.VU) modules.Instance { + return &ModuleInstance{vu: m} } // New returns a new RootModule. @@ -158,9 +158,16 @@ func New() *RootModule { return &RootModule{} } -// GetExports returns the exports of the metrics module -func (mi *ModuleInstance) GetExports() modules.Exports { - return modules.GenerateExports(mi) +// Exports returns the exports of the metrics module +func (mi *ModuleInstance) Exports() modules.Exports { + return modules.Exports{ + Named: map[string]interface{}{ + "Counter": mi.XCounter, + "Gauge": mi.XGauge, + "Trend": mi.XTrend, + "Rate": mi.XRate, + }, + } } // XCounter is a counter constructor diff --git a/js/modules/k6/metrics/metrics_test.go b/js/modules/k6/metrics/metrics_test.go index 6d192ee7b1d..a8e4dd8f423 100644 --- a/js/modules/k6/metrics/metrics_test.go +++ b/js/modules/k6/metrics/metrics_test.go @@ -121,14 +121,14 @@ func TestMetrics(t *testing.T) { } test.rt = goja.New() test.rt.SetFieldNameMapper(common.FieldNameMapper{}) - mii := &modulestest.InstanceCore{ - Runtime: test.rt, - InitEnv: &common.InitEnvironment{Registry: metrics.NewRegistry()}, - Ctx: context.Background(), + mii := &modulestest.VU{ + RuntimeField: test.rt, + InitEnvField: &common.InitEnvironment{Registry: metrics.NewRegistry()}, + CtxField: context.Background(), } m, ok := New().NewModuleInstance(mii).(*ModuleInstance) require.True(t, ok) - require.NoError(t, test.rt.Set("metrics", m.GetExports().Named)) + require.NoError(t, test.rt.Set("metrics", m.Exports().Named)) test.samples = make(chan stats.SampleContainer, 1000) state := &lib.State{ Options: lib.Options{}, @@ -146,12 +146,12 @@ func TestMetrics(t *testing.T) { require.NoError(t, err) t.Run("ExitInit", func(t *testing.T) { - mii.State = state - mii.InitEnv = nil + mii.StateField = state + mii.InitEnvField = nil _, err := test.rt.RunString(fmt.Sprintf(`new metrics.%s("my_metric")`, fn)) assert.Contains(t, err.Error(), "metrics must be declared in the init context") }) - mii.State = state + mii.StateField = state logger := logrus.New() logger.Out = ioutil.Discard test.hook = &testutils.SimpleLogrusHook{HookedLevels: logrus.AllLevels} @@ -186,14 +186,14 @@ func TestMetricGetName(t *testing.T) { rt := goja.New() rt.SetFieldNameMapper(common.FieldNameMapper{}) - mii := &modulestest.InstanceCore{ - Runtime: rt, - InitEnv: &common.InitEnvironment{Registry: metrics.NewRegistry()}, - Ctx: context.Background(), + mii := &modulestest.VU{ + RuntimeField: rt, + InitEnvField: &common.InitEnvironment{Registry: metrics.NewRegistry()}, + CtxField: context.Background(), } m, ok := New().NewModuleInstance(mii).(*ModuleInstance) require.True(t, ok) - require.NoError(t, rt.Set("metrics", m.GetExports().Named)) + require.NoError(t, rt.Set("metrics", m.Exports().Named)) v, err := rt.RunString(` var m = new metrics.Counter("my_metric") m.name @@ -214,14 +214,14 @@ func TestMetricDuplicates(t *testing.T) { rt := goja.New() rt.SetFieldNameMapper(common.FieldNameMapper{}) - mii := &modulestest.InstanceCore{ - Runtime: rt, - InitEnv: &common.InitEnvironment{Registry: metrics.NewRegistry()}, - Ctx: context.Background(), + mii := &modulestest.VU{ + RuntimeField: rt, + InitEnvField: &common.InitEnvironment{Registry: metrics.NewRegistry()}, + CtxField: context.Background(), } m, ok := New().NewModuleInstance(mii).(*ModuleInstance) require.True(t, ok) - require.NoError(t, rt.Set("metrics", m.GetExports().Named)) + require.NoError(t, rt.Set("metrics", m.Exports().Named)) _, err := rt.RunString(` var m = new metrics.Counter("my_metric") `) diff --git a/js/modules/modules.go b/js/modules/modules.go index 5c940b07e17..34f8d436ebe 100644 --- a/js/modules/modules.go +++ b/js/modules/modules.go @@ -65,12 +65,11 @@ type HasModuleInstancePerVU interface { NewModuleInstancePerVU() interface{} } -// IsModuleV2 is the interface js modules should implement to get the version 2 of the system -type IsModuleV2 interface { - // NewModuleInstance will get InstanceCore that should provide the module with *everything* it needs and return an - // Instance implementation (embedding the InstanceCore). - // This method will be called for *each* require/import and return an object for VUs. - NewModuleInstance(InstanceCore) Instance +// Module is the interface js modules should implement in order to get access to the VU +type Module interface { + // NewModuleInstance will get modules.VU that should provide the module with a way to interact with the VU + // This method will be called for *each* require/import and should return an unique instance for each call + NewModuleInstance(VU) Instance } // checks that modules implement HasModuleInstancePerVU @@ -92,8 +91,7 @@ func GetJSModules() map[string]interface{} { // Instance is what a module needs to return type Instance interface { - InstanceCore - GetExports() Exports + Exports() Exports } func getInterfaceMethods() []string { @@ -108,21 +106,19 @@ func getInterfaceMethods() []string { return result } -// InstanceCore is something that will be provided to modules and they need to embed it in ModuleInstance -type InstanceCore interface { - GetContext() context.Context +// VU gives access to the currently executing VU to a module Instance +type VU interface { + // Context return the context.Context about the current VU + Context() context.Context - // GetInitEnv returns common.InitEnvironment instance if present - GetInitEnv() *common.InitEnvironment + // InitEnv returns common.InitEnvironment instance if present + InitEnv() *common.InitEnvironment - // GetState returns lib.State if any is present - GetState() *lib.State + // State returns lib.State if any is present + State() *lib.State - // GetRuntime returns the goja.Runtime for the current VU - GetRuntime() *goja.Runtime - - // sealing field will help probably with pointing users that they just need to embed this in their Instance - // implementations + // Runtime returns the goja.Runtime for the current VU + Runtime() *goja.Runtime } // Exports is representation of ESM exports of a module @@ -132,44 +128,3 @@ type Exports struct { // Named is the named exports of a module Named map[string]interface{} } - -// GenerateExports generates an Exports from a module akin to how common.Bind does now. -// it also skips anything that is expected will not want to be exported such as methods and fields coming from -// interfaces defined in this package. -func GenerateExports(v interface{}) Exports { - exports := make(map[string]interface{}) - val := reflect.ValueOf(v) - typ := val.Type() - badNames := getInterfaceMethods() -outer: - for i := 0; i < typ.NumMethod(); i++ { - meth := typ.Method(i) - for _, badname := range badNames { - if meth.Name == badname { - continue outer - } - } - name := common.MethodName(typ, meth) - - fn := val.Method(i) - exports[name] = fn.Interface() - } - - // If v is a pointer, we need to indirect it to access its fields. - if typ.Kind() == reflect.Ptr { - val = val.Elem() - typ = val.Type() - } - var mic InstanceCore // TODO move this out - for i := 0; i < typ.NumField(); i++ { - field := typ.Field(i) - if field.Type == reflect.TypeOf(&mic).Elem() { - continue - } - name := common.FieldName(typ, field) - if name != "" { - exports[name] = val.Field(i).Interface() - } - } - return Exports{Default: exports, Named: exports} -} diff --git a/js/modulestest/modulestest.go b/js/modulestest/modulestest.go index e00cae7c7df..cf3574d5cfa 100644 --- a/js/modulestest/modulestest.go +++ b/js/modulestest/modulestest.go @@ -29,32 +29,32 @@ import ( "go.k6.io/k6/lib" ) -var _ modules.InstanceCore = &InstanceCore{} - -// InstanceCore is a modules.InstanceCore implementation meant to be used within tests -type InstanceCore struct { - Ctx context.Context - InitEnv *common.InitEnvironment - State *lib.State - Runtime *goja.Runtime +var _ modules.VU = &VU{} + +// VU is a modules.VU implementation meant to be used within tests +type VU struct { + CtxField context.Context + InitEnvField *common.InitEnvironment + StateField *lib.State + RuntimeField *goja.Runtime } -// GetContext returns internally set field to conform to modules.InstanceCore interface -func (m *InstanceCore) GetContext() context.Context { - return m.Ctx +// Context returns internally set field to conform to modules.VU interface +func (m *VU) Context() context.Context { + return m.CtxField } -// GetInitEnv returns internally set field to conform to modules.InstanceCore interface -func (m *InstanceCore) GetInitEnv() *common.InitEnvironment { - return m.InitEnv +// InitEnv returns internally set field to conform to modules.VU interface +func (m *VU) InitEnv() *common.InitEnvironment { + return m.InitEnvField } -// GetState returns internally set field to conform to modules.InstanceCore interface -func (m *InstanceCore) GetState() *lib.State { - return m.State +// State returns internally set field to conform to modules.VU interface +func (m *VU) State() *lib.State { + return m.StateField } -// GetRuntime returns internally set field to conform to modules.InstanceCore interface -func (m *InstanceCore) GetRuntime() *goja.Runtime { - return m.Runtime +// Runtime returns internally set field to conform to modules.VU interface +func (m *VU) Runtime() *goja.Runtime { + return m.RuntimeField }