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

Refactor k6/http and k6/html to the new JS Module API #2226

Merged
merged 1 commit into from
Dec 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ linters-settings:
min-confidence: 0
gocyclo:
min-complexity: 25
cyclop: #TODO: see https://github.com/grafana/k6/issues/2294, leave only one of these?
max-complexity: 25
maligned:
suggest-new: true
dupl:
Expand Down
16 changes: 5 additions & 11 deletions js/modules/k6/html/element_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@
package html

import (
"context"
"testing"

"github.com/dop251/goja"
"github.com/stretchr/testify/assert"

"go.k6.io/k6/js/common"
"github.com/stretchr/testify/require"
)

const testHTMLElem = `
Expand Down Expand Up @@ -55,17 +53,13 @@ const testHTMLElem = `
`

func TestElement(t *testing.T) {
rt := goja.New()
rt.SetFieldNameMapper(common.FieldNameMapper{})

ctx := common.WithRuntime(context.Background(), rt)
rt.Set("src", testHTMLElem)
rt.Set("html", common.Bind(rt, &HTML{}, &ctx))
// compileProtoElem()
t.Parallel()
rt, _ := getTestModuleInstance(t)
require.NoError(t, rt.Set("src", testHTMLElem))

_, err := rt.RunString(`var doc = html.parseHTML(src)`)

assert.NoError(t, err)
require.NoError(t, err)
assert.IsType(t, Selection{}, rt.Get("doc").Export())

t.Run("NodeName", func(t *testing.T) {
Expand Down
19 changes: 6 additions & 13 deletions js/modules/k6/html/elements_gen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,10 @@
package html

import (
"context"
"testing"

"github.com/dop251/goja"
"github.com/stretchr/testify/assert"

"go.k6.io/k6/js/common"
"github.com/stretchr/testify/require"
)

var textTests = []struct {
Expand Down Expand Up @@ -404,16 +401,13 @@ const testGenElems = `<html><body>
`

func TestGenElements(t *testing.T) {
rt := goja.New()
rt.SetFieldNameMapper(common.FieldNameMapper{})

ctx := common.WithRuntime(context.Background(), rt)
rt.Set("src", testGenElems)
rt.Set("html", common.Bind(rt, &HTML{}, &ctx))
t.Parallel()
rt, mi := getTestModuleInstance(t)
require.NoError(t, rt.Set("src", testGenElems))

_, err := rt.RunString("var doc = html.parseHTML(src)")

assert.NoError(t, err)
require.NoError(t, err)
assert.IsType(t, Selection{}, rt.Get("doc").Export())

t.Run("Test text properties", func(t *testing.T) {
Expand Down Expand Up @@ -468,8 +462,7 @@ func TestGenElements(t *testing.T) {
})

t.Run("Test url properties", func(t *testing.T) {
html := HTML{}
sel, parseError := html.ParseHTML(ctx, testGenElems)
sel, parseError := mi.parseHTML(testGenElems)
if parseError != nil {
t.Errorf("Unable to parse html")
}
Expand Down
15 changes: 5 additions & 10 deletions js/modules/k6/html/elements_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@
package html

import (
"context"
"testing"

"github.com/dop251/goja"
"github.com/stretchr/testify/assert"

"go.k6.io/k6/js/common"
"github.com/stretchr/testify/require"
)

const testHTMLElems = `
Expand Down Expand Up @@ -86,16 +84,13 @@ const testHTMLElems = `
`

func TestElements(t *testing.T) {
rt := goja.New()
rt.SetFieldNameMapper(common.FieldNameMapper{})

ctx := common.WithRuntime(context.Background(), rt)
rt.Set("src", testHTMLElems)
rt.Set("html", common.Bind(rt, &HTML{}, &ctx))
t.Parallel()
rt, _ := getTestModuleInstance(t)
require.NoError(t, rt.Set("src", testHTMLElems))

_, err := rt.RunString(`var doc = html.parseHTML(src)`)

assert.NoError(t, err)
require.NoError(t, err)
assert.IsType(t, Selection{}, rt.Get("doc").Export())

t.Run("AnchorElement", func(t *testing.T) {
Expand Down
53 changes: 47 additions & 6 deletions js/modules/k6/html/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
package html

import (
"context"
"errors"
"fmt"
"strings"
Expand All @@ -31,20 +30,62 @@ import (
gohtml "golang.org/x/net/html"

"go.k6.io/k6/js/common"
"go.k6.io/k6/js/modules"
)

type HTML struct{}
// RootModule is the global module object type. It is instantiated once per test
// run and will be used to create k6/html module instances for each VU.
type RootModule struct{}

func New() *HTML {
return &HTML{}
// ModuleInstance represents an instance of the HTML module for every VU.
type ModuleInstance struct {
vu modules.VU
rootModule *RootModule
exports *goja.Object
}

func (HTML) ParseHTML(ctx context.Context, src string) (Selection, error) {
var (
_ modules.Module = &RootModule{}
_ modules.Instance = &ModuleInstance{}
)

// New returns a pointer to a new HTML RootModule.
func New() *RootModule {
return &RootModule{}
}

// Exports returns the JS values this module exports.
func (mi *ModuleInstance) Exports() modules.Exports {
return modules.Exports{
Default: mi.exports,
}
}

// NewModuleInstance returns an HTML module instance for each VU.
func (r *RootModule) NewModuleInstance(vu modules.VU) modules.Instance {
rt := vu.Runtime()
mi := &ModuleInstance{
vu: vu,
rootModule: r,
exports: rt.NewObject(),
}
if err := mi.exports.Set("parseHTML", mi.parseHTML); err != nil {
common.Throw(rt, err)
}
return mi
}

func (mi *ModuleInstance) parseHTML(src string) (Selection, error) {
return ParseHTML(mi.vu.Runtime(), src)
}

// ParseHTML parses the provided HTML source into a Selection object.
func ParseHTML(rt *goja.Runtime, src string) (Selection, error) {
codebien marked this conversation as resolved.
Show resolved Hide resolved
doc, err := goquery.NewDocumentFromReader(strings.NewReader(src))
if err != nil {
return Selection{}, err
}
return Selection{rt: common.GetRuntime(ctx), sel: doc.Selection}, nil
return Selection{rt: rt, sel: doc.Selection}, nil
}

type Selection struct {
Expand Down
37 changes: 32 additions & 5 deletions js/modules/k6/html/html_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ import (

"github.com/dop251/goja"
"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/metrics"
)

const testHTML = `
Expand Down Expand Up @@ -63,18 +66,42 @@ const testHTML = `
</body>
`

func TestParseHTML(t *testing.T) {
func getTestModuleInstance(t testing.TB) (*goja.Runtime, *ModuleInstance) {
rt := goja.New()
rt.SetFieldNameMapper(common.FieldNameMapper{})
ctx := common.WithRuntime(context.Background(), rt)
rt.Set("src", testHTML)
rt.Set("html", common.Bind(rt, New(), &ctx))

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)

root := New()
mockVU := &modulestest.VU{
RuntimeField: rt,
InitEnvField: &common.InitEnvironment{
Registry: metrics.NewRegistry(),
},
CtxField: ctx,
StateField: nil,
}
mi, ok := root.NewModuleInstance(mockVU).(*ModuleInstance)
require.True(t, ok)

require.NoError(t, rt.Set("html", mi.Exports().Default))

return rt, mi
}

// TODO: split apart?
// nolint: cyclop, tparallel
func TestParseHTML(t *testing.T) {
t.Parallel()
rt, _ := getTestModuleInstance(t)
require.NoError(t, rt.Set("src", testHTML))

// TODO: I literally cannot think of a snippet that makes goquery error.
// I'm not sure if it's even possible without like, an invalid reader or something, which would
// be impossible to cause from the JS side.
_, err := rt.RunString(`var doc = html.parseHTML(src)`)
assert.NoError(t, err)
require.NoError(t, err)
assert.IsType(t, Selection{}, rt.Get("doc").Export())

t.Run("Find", func(t *testing.T) {
Expand Down
14 changes: 5 additions & 9 deletions js/modules/k6/html/serialize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@
package html

import (
"context"
"testing"

"github.com/dop251/goja"
"github.com/stretchr/testify/assert"

"go.k6.io/k6/js/common"
"github.com/stretchr/testify/require"
)

const testSerializeHTML = `
Expand Down Expand Up @@ -69,14 +67,12 @@ const testSerializeHTML = `
`

func TestSerialize(t *testing.T) {
rt := goja.New()
rt.SetFieldNameMapper(common.FieldNameMapper{})
ctx := common.WithRuntime(context.Background(), rt)
rt.Set("src", testSerializeHTML)
rt.Set("html", common.Bind(rt, New(), &ctx))
t.Parallel()
rt, _ := getTestModuleInstance(t)
require.NoError(t, rt.Set("src", testSerializeHTML))

_, err := rt.RunString(`var doc = html.parseHTML(src)`)
assert.NoError(t, err)
require.NoError(t, err)
assert.IsType(t, Selection{}, rt.Get("doc").Export())

t.Run("SerializeArray", func(t *testing.T) {
Expand Down
29 changes: 12 additions & 17 deletions js/modules/k6/http/cookiejar.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
package http

import (
"context"
"fmt"
"net/http"
"net/http/cookiejar"
Expand All @@ -30,27 +29,23 @@ import (
"time"

"github.com/dop251/goja"

"go.k6.io/k6/js/common"
)

// HTTPCookieJar is cookiejar.Jar wrapper to be used in js scripts
type HTTPCookieJar struct {
// js is to make it not be accessible from inside goja/js, the json is because it's used if we return it from setup
Jar *cookiejar.Jar `js:"-" json:"-"`
ctx *context.Context
}
// ErrJarForbiddenInInitContext is used when a cookie jar was made in the init context
// TODO: unexport this? there's no reason for this to be exported
var ErrJarForbiddenInInitContext = common.NewInitContextError("Making cookie jars in the init context is not supported")
na-- marked this conversation as resolved.
Show resolved Hide resolved

func newCookieJar(ctxPtr *context.Context) *HTTPCookieJar {
jar, err := cookiejar.New(nil)
if err != nil {
common.Throw(common.GetRuntime(*ctxPtr), err)
}
return &HTTPCookieJar{jar, ctxPtr}
// CookieJar is cookiejar.Jar wrapper to be used in js scripts
type CookieJar struct {
moduleInstance *ModuleInstance
// js is to make it not be accessible from inside goja/js, the json is
// for when it is returned from setup().
Jar *cookiejar.Jar `js:"-" json:"-"`
}

// CookiesForURL return the cookies for a given url as a map of key and values
func (j HTTPCookieJar) CookiesForURL(url string) map[string][]string {
func (j CookieJar) CookiesForURL(url string) map[string][]string {
u, err := neturl.Parse(url)
if err != nil {
panic(err)
Expand All @@ -65,8 +60,8 @@ func (j HTTPCookieJar) CookiesForURL(url string) map[string][]string {
}

// Set sets a cookie for a particular url with the given name value and additional opts
func (j HTTPCookieJar) Set(url, name, value string, opts goja.Value) (bool, error) {
rt := common.GetRuntime(*j.ctx)
func (j CookieJar) Set(url, name, value string, opts goja.Value) (bool, error) {
rt := j.moduleInstance.vu.Runtime()

u, err := neturl.Parse(url)
if err != nil {
Expand Down
7 changes: 3 additions & 4 deletions js/modules/k6/http/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
package http

import (
"context"
"fmt"
"strings"
"time"
Expand All @@ -42,8 +41,8 @@ func escapeQuotes(s string) string {
return quoteEscaper.Replace(s)
}

// File returns a FileData parameter
func (h *HTTP) File(ctx context.Context, data interface{}, args ...string) FileData {
// File returns a FileData object.
func (mi *ModuleInstance) file(data interface{}, args ...string) FileData {
// supply valid default if filename and content-type are not specified
fname, ct := fmt.Sprintf("%d", time.Now().UnixNano()), "application/octet-stream"

Expand All @@ -57,7 +56,7 @@ func (h *HTTP) File(ctx context.Context, data interface{}, args ...string) FileD

dt, err := common.ToBytes(data)
if err != nil {
common.Throw(common.GetRuntime(ctx), err)
common.Throw(mi.vu.Runtime(), err)
}

return FileData{
Expand Down
Loading