Skip to content

Commit

Permalink
Add support for xk6 plugins
Browse files Browse the repository at this point in the history
This adds plugin support to k6 inspired by xcaddy[1]. See the xk6 repo[2].

Closes #1353

[1]: https://github.com/caddyserver/xcaddy
[2]: https://github.com/k6io/xk6
  • Loading branch information
Ivan Mirić authored and imiric committed Nov 4, 2020
1 parent 4385b52 commit df5087c
Show file tree
Hide file tree
Showing 16 changed files with 187 additions and 55 deletions.
13 changes: 7 additions & 6 deletions js/initcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (

"github.com/loadimpact/k6/js/common"
"github.com/loadimpact/k6/js/compiler"
"github.com/loadimpact/k6/js/modules"
"github.com/loadimpact/k6/js/internal/modules"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/loader"
)
Expand Down Expand Up @@ -115,8 +115,9 @@ func newBoundInitContext(base *InitContext, ctxPtr *context.Context, rt *goja.Ru
func (i *InitContext) Require(arg string) goja.Value {
switch {
case arg == "k6", strings.HasPrefix(arg, "k6/"):
// Builtin modules ("k6" or "k6/...") are handled specially, as they don't exist on the
// filesystem. This intentionally shadows attempts to name your own modules this.
// Builtin or external modules ("k6", "k6/*", or "k6/x/*") are handled
// specially, as they don't exist on the filesystem. This intentionally
// shadows attempts to name your own modules this.
v, err := i.requireModule(arg)
if err != nil {
common.Throw(i.runtime, err)
Expand All @@ -133,9 +134,9 @@ func (i *InitContext) Require(arg string) goja.Value {
}

func (i *InitContext) requireModule(name string) (goja.Value, error) {
mod, ok := modules.Index[name]
if !ok {
return nil, errors.Errorf("unknown builtin module: %s", name)
mod := modules.Get(name)
if mod == nil {
return nil, errors.Errorf("unknown module: %s", name)
}
return i.runtime.ToValue(common.Bind(i.runtime, mod, i.ctxPtr)), nil
}
Expand Down
2 changes: 1 addition & 1 deletion js/initcontext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestInitContextRequire(t *testing.T) {
t.Run("Modules", func(t *testing.T) {
t.Run("Nonexistent", func(t *testing.T) {
_, err := getSimpleBundle(t, "/script.js", `import "k6/NONEXISTENT";`)
assert.Contains(t, err.Error(), "GoError: unknown builtin module: k6/NONEXISTENT")
assert.Contains(t, err.Error(), "GoError: unknown module: k6/NONEXISTENT")
})

t.Run("k6", func(t *testing.T) {
Expand Down
52 changes: 52 additions & 0 deletions js/internal/modules/modules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2020 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package modules

import (
"fmt"
"sync"
)

//nolint:gochecknoglobals
var (
modules = make(map[string]interface{})
mx sync.RWMutex
)

// Get returns the module registered with name.
func Get(name string) interface{} {
mx.RLock()
defer mx.RUnlock()
return modules[name]
}

// Register the given mod as a JavaScript module, available
// for import from JS scripts by name.
// This function panics if a module with the same name is already registered.
func Register(name string, mod interface{}) {
mx.Lock()
defer mx.Unlock()

if _, ok := modules[name]; ok {
panic(fmt.Sprintf("module already registered: %s", name))
}
modules[name] = mod
}
33 changes: 33 additions & 0 deletions js/modules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2020 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package js

import (
// Initialize all internal JS modules.
_ "github.com/loadimpact/k6/js/modules/k6"
_ "github.com/loadimpact/k6/js/modules/k6/crypto"
_ "github.com/loadimpact/k6/js/modules/k6/crypto/x509"
_ "github.com/loadimpact/k6/js/modules/k6/encoding"
_ "github.com/loadimpact/k6/js/modules/k6/grpc"
_ "github.com/loadimpact/k6/js/modules/k6/http"
_ "github.com/loadimpact/k6/js/modules/k6/metrics"
_ "github.com/loadimpact/k6/js/modules/k6/ws"
)
46 changes: 0 additions & 46 deletions js/modules/index.go

This file was deleted.

5 changes: 5 additions & 0 deletions js/modules/k6/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ import (
"golang.org/x/crypto/ripemd160"

"github.com/loadimpact/k6/js/common"
"github.com/loadimpact/k6/js/internal/modules"
)

func init() {
modules.Register("k6/crypto", New())
}

type Crypto struct{}

type Hasher struct {
Expand Down
5 changes: 5 additions & 0 deletions js/modules/k6/crypto/x509/x509.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,13 @@ import (
"github.com/pkg/errors"

"github.com/loadimpact/k6/js/common"
"github.com/loadimpact/k6/js/internal/modules"
)

func init() {
modules.Register("k6/crypto/x509", New())
}

// X509 certificate functionality
type X509 struct{}

Expand Down
5 changes: 5 additions & 0 deletions js/modules/k6/encoding/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ import (
"encoding/base64"

"github.com/loadimpact/k6/js/common"
"github.com/loadimpact/k6/js/internal/modules"
)

func init() {
modules.Register("k6/encoding", New())
}

type Encoding struct{}

func New() *Encoding {
Expand Down
4 changes: 2 additions & 2 deletions js/modules/k6/grpc/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func TestClient(t *testing.T) {

t.Run("LoadNotFound", func(t *testing.T) {
_, err := common.RunString(rt, `
client.load([], "./does_not_exist.proto");
client.load([], "./does_not_exist.proto");
`)
if !assert.Error(t, err) {
return
Expand All @@ -115,7 +115,7 @@ func TestClient(t *testing.T) {

t.Run("Load", func(t *testing.T) {
respV, err := common.RunString(rt, `
client.load([], "../../../../vendor/google.golang.org/grpc/test/grpc_testing/test.proto");
client.load([], "../../../../vendor/google.golang.org/grpc/test/grpc_testing/test.proto");
`)
if !assert.NoError(t, err) {
return
Expand Down
6 changes: 6 additions & 0 deletions js/modules/k6/grpc/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ package grpc

import (
"google.golang.org/grpc/codes"

"github.com/loadimpact/k6/js/internal/modules"
)

func init() {
modules.Register("k6/protocols/grpc", New())
}

// GRPC represents the gRPC protocol module for k6
type GRPC struct {
StatusOK codes.Code `js:"StatusOK"`
Expand Down
5 changes: 5 additions & 0 deletions js/modules/k6/html/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,13 @@ import (
gohtml "golang.org/x/net/html"

"github.com/loadimpact/k6/js/common"
"github.com/loadimpact/k6/js/internal/modules"
)

func init() {
modules.Register("k6/html", New())
}

type HTML struct{}

func New() *HTML {
Expand Down
5 changes: 5 additions & 0 deletions js/modules/k6/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@ import (
"context"

"github.com/loadimpact/k6/js/common"
"github.com/loadimpact/k6/js/internal/modules"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/lib/netext"
)

func init() {
modules.Register("k6/http", New())
}

const (
HTTP_METHOD_GET = "GET"
HTTP_METHOD_POST = "POST"
Expand Down
5 changes: 5 additions & 0 deletions js/modules/k6/k6.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,16 @@ import (
"github.com/pkg/errors"

"github.com/loadimpact/k6/js/common"
"github.com/loadimpact/k6/js/internal/modules"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/lib/metrics"
"github.com/loadimpact/k6/stats"
)

func init() {
modules.Register("k6", New())
}

type K6 struct{}

// ErrGroupInInitContext is returned when group() are using in the init context
Expand Down
5 changes: 5 additions & 0 deletions js/modules/k6/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,15 @@ import (
"github.com/dop251/goja"

"github.com/loadimpact/k6/js/common"
"github.com/loadimpact/k6/js/internal/modules"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/stats"
)

func init() {
modules.Register("k6/metrics", New())
}

var nameRegexString = "^[\\p{L}\\p{N}\\._ !\\?/&#\\(\\)<>%-]{1,128}$"

var compileNameRegex = regexp.MustCompile(nameRegexString)
Expand Down
5 changes: 5 additions & 0 deletions js/modules/k6/ws/ws.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@ import (
"github.com/gorilla/websocket"

"github.com/loadimpact/k6/js/common"
"github.com/loadimpact/k6/js/internal/modules"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/lib/metrics"
"github.com/loadimpact/k6/stats"
)

func init() {
modules.Register("k6/ws", New())
}

// ErrWSInInitContext is returned when websockets are using in the init context
var ErrWSInInitContext = common.NewInitContextError("using websockets in the init context is not supported")

Expand Down
46 changes: 46 additions & 0 deletions js/modules/modules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2020 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package modules

import (
"fmt"
"strings"

"github.com/loadimpact/k6/js/internal/modules"
)

const extPrefix string = "k6/x/"

// Get returns the module registered with name.
func Get(name string) interface{} {
return modules.Get(name)
}

// Register the given mod as an external JavaScript module that can be imported
// by name. The name must be unique across all registered modules and must be
// prefixed with "k6/x/", otherwise this function will panic.
func Register(name string, mod interface{}) {
if !strings.HasPrefix(name, extPrefix) {
panic(fmt.Errorf("external module names must be prefixed with '%s', tried to register: %s", extPrefix, name))
}

modules.Register(name, mod)
}

0 comments on commit df5087c

Please sign in to comment.