Skip to content

Commit

Permalink
fixup! js/k6/exec: Allow to set state's Tags
Browse files Browse the repository at this point in the history
  • Loading branch information
codebien committed Nov 2, 2021
1 parent 36541d3 commit 0c2a369
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 54 deletions.
48 changes: 39 additions & 9 deletions js/modules/k6/execution/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ package execution

import (
"errors"
"fmt"
"reflect"
"time"

"github.com/dop251/goja"
Expand Down Expand Up @@ -200,7 +202,7 @@ func (mi *ModuleInstance) newVUInfo() (*goja.Object, error) {

err = o.Set("tags", rt.NewDynamicObject(&tagsDynamicObject{
Runtime: rt,
Tags: vuState.Tags,
State: vuState,
}))
return o, err
}
Expand All @@ -220,12 +222,12 @@ func newInfoObj(rt *goja.Runtime, props map[string]func() interface{}) (*goja.Ob

type tagsDynamicObject struct {
Runtime *goja.Runtime
Tags *lib.TagMap
State *lib.State
}

// Get a property value for the key. May return nil if the property does not exist.
func (o *tagsDynamicObject) Get(key string) goja.Value {
tag, ok := o.Tags.Get(key)
tag, ok := o.State.Tags.Get(key)
if !ok {
return nil
}
Expand All @@ -236,29 +238,57 @@ func (o *tagsDynamicObject) Get(key string) goja.Value {
// If a type different from a string is passed as value then
// it will be implicitly converted to the goja's relative string representation.
func (o *tagsDynamicObject) Set(key string, val goja.Value) bool {
o.Tags.Set(key, val.String())
return true
switch val.ExportType().Kind() {
case
reflect.String,
reflect.Bool,
reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64,
reflect.Float32,
reflect.Float64,
reflect.Complex64,
reflect.Complex128:

o.State.Tags.Set(key, val.String())
return true
default:
err := fmt.Errorf("only String, Boolean and Number types are accepted as a Tag value")
if o.State.Options.Throw.Bool {
common.Throw(o.Runtime, err)
return false
}
o.State.Logger.Warn("the Set operation has been discarded because" + err.Error())
return false
}
}

// Has returns true if the property exists.
func (o *tagsDynamicObject) Has(key string) bool {
_, ok := o.Tags.Get(key)
_, ok := o.State.Tags.Get(key)
return ok
}

// Delete deletes the property for the key. It returns true on success (note, that includes missing property).
func (o *tagsDynamicObject) Delete(key string) bool {
o.Tags.Delete(key)
o.State.Tags.Delete(key)
return true
}

// Keys returns a slice with all existing property keys. The order is not deterministic.
func (o *tagsDynamicObject) Keys() []string {
if o.Tags.Len() < 1 {
if o.State.Tags.Len() < 1 {
return nil
}

tags := o.Tags.Clone()
tags := o.State.Tags.Clone()
keys := make([]string, 0, len(tags))
for k := range tags {
keys = append(keys, k)
Expand Down
172 changes: 127 additions & 45 deletions js/modules/k6/execution/execution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,64 +22,146 @@ package execution

import (
"context"
"io/ioutil"
"testing"

"github.com/dop251/goja"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.k6.io/k6/js/common"
"go.k6.io/k6/js/modulestest"
"go.k6.io/k6/lib"
"go.k6.io/k6/lib/testutils"
"go.k6.io/k6/stats"
"gopkg.in/guregu/null.v3"
)

func TestVUTags(t *testing.T) {
t.Parallel()

rt := goja.New()
ctx := common.WithRuntime(context.Background(), rt)
ctx = lib.WithState(ctx, &lib.State{
Options: lib.Options{
SystemTags: stats.NewSystemTagSet(stats.TagVU),
},
Tags: lib.NewTagMap(map[string]string{
"vu": "42",
}),
setupExecModule := func(t *testing.T) (*ModuleInstance, *goja.Runtime, *testutils.SimpleLogrusHook) {
logHook := &testutils.SimpleLogrusHook{HookedLevels: []logrus.Level{logrus.WarnLevel}}
testLog := logrus.New()
testLog.AddHook(logHook)
testLog.SetOutput(ioutil.Discard)

state := &lib.State{
Options: lib.Options{
SystemTags: stats.NewSystemTagSet(stats.TagVU),
},
Tags: lib.NewTagMap(map[string]string{
"vu": "42",
}),
Logger: testLog,
}

rt := goja.New()
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,
},
).(*ModuleInstance)
require.True(t, ok)
require.NoError(t, rt.Set("exec", m.GetExports().Default))

return m, rt, logHook
}

t.Run("Get", func(t *testing.T) {
t.Parallel()

_, rt, _ := setupExecModule(t)
tag, err := rt.RunString(`exec.vu.tags["vu"]`)
require.NoError(t, err)
assert.Equal(t, "42", tag.String())

// not found
tag, err = rt.RunString(`exec.vu.tags["not-existing-tag"]`)
require.NoError(t, err)
assert.Equal(t, "undefined", tag.String())
})

t.Run("JSONEncoding", func(t *testing.T) {
t.Parallel()

mi, rt, _ := setupExecModule(t)
state := mi.GetState()
state.Tags.Set("custom-tag", "mytag1")

encoded, err := rt.RunString(`JSON.stringify(exec.vu.tags)`)
require.NoError(t, err)
assert.JSONEq(t, `{"vu":"42","custom-tag":"mytag1"}`, encoded.String())
})

t.Run("Set", func(t *testing.T) {
t.Parallel()

t.Run("SuccessAccetedTypes", func(t *testing.T) {
t.Parallel()

_, rt, _ := setupExecModule(t)

_, err := rt.RunString(`exec.vu.tags["custom-tag"] = "mytag"`)
require.NoError(t, err)

// bool and numbers are implicitly converted into string

_, err = rt.RunString(`exec.vu.tags["vu"] = true`)
require.NoError(t, err)
val, err := rt.RunString(`exec.vu.tags["vu"]`)
require.NoError(t, err)
assert.Equal(t, "true", val.String())

_, err = rt.RunString(`exec.vu.tags["vu"] = 101`)
require.NoError(t, err)
val, err = rt.RunString(`exec.vu.tags["vu"]`)
require.NoError(t, err)
assert.Equal(t, "101", val.String())
})

t.Run("SuccessOverwriteSystemTag", func(t *testing.T) {
t.Parallel()

_, rt, _ := setupExecModule(t)

_, err := rt.RunString(`exec.vu.tags["vu"] = "vu101"`)
require.NoError(t, err)
val, err := rt.RunString(`exec.vu.tags["vu"]`)
require.NoError(t, err)
assert.Equal(t, "vu101", val.String())
})

t.Run("DiscardWrongTypeRaisingError", func(t *testing.T) {
t.Parallel()

mi, rt, _ := setupExecModule(t)
state := mi.GetState()
state.Options.Throw = null.BoolFrom(true)
require.NotNil(t, state)

// array
_, err := rt.RunString(`exec.vu.tags["custom-tag"] = [1, 3, 5]`)
require.Contains(t, err.Error(), "only String, Boolean and Number")

// object
_, err = rt.RunString(`exec.vu.tags["custom-tag"] = {f1: "value1", f2: 4}`)
require.Contains(t, err.Error(), "only String, Boolean and Number")
})

t.Run("DiscardWrongTypeOnlyWarning", func(t *testing.T) {
_, rt, logHook := setupExecModule(t)
_, err := rt.RunString(`exec.vu.tags["custom-tag"] = [1, 3, 5]`)
require.NoError(t, err)

entries := logHook.Drain()
require.Len(t, entries, 1)
assert.Contains(t, entries[0].Message, "discarded")
})
})
m, ok := New().NewModuleInstance(
&modulestest.InstanceCore{
Runtime: rt,
InitEnv: &common.InitEnvironment{},
Ctx: ctx,
},
).(*ModuleInstance)
require.True(t, ok)
require.NoError(t, rt.Set("exec", m.GetExports().Default))

// overwrite a system tag is allowed
_, err := rt.RunString(`exec.vu.tags["vu"] = 101`)
require.NoError(t, err)
val, err := rt.RunString(`exec.vu.tags["vu"]`)
require.NoError(t, err)
// different type from string are implicitly converted into string
assert.Equal(t, "101", val.String())

// not found
tag, err := rt.RunString(`exec.vu.tags["not-existing-tag"]`)
require.NoError(t, err)
assert.Equal(t, "undefined", tag.String())

// set
_, err = rt.RunString(`exec.vu.tags["custom-tag"] = "mytag"`)
require.NoError(t, err)

// get
tag, err = rt.RunString(`exec.vu.tags["custom-tag"]`)
require.NoError(t, err)
assert.Equal(t, "mytag", tag.String())

// json encoding
encoded, err := rt.RunString(`JSON.stringify(exec.vu.tags)`)
require.NoError(t, err)
assert.JSONEq(t, `{"vu":"101","custom-tag":"mytag"}`, encoded.String())
}

0 comments on commit 0c2a369

Please sign in to comment.