diff --git a/js/initcontext.go b/js/initcontext.go index 11335e9bd02..93241630519 100644 --- a/js/initcontext.go +++ b/js/initcontext.go @@ -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" ) @@ -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) @@ -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 } diff --git a/js/initcontext_test.go b/js/initcontext_test.go index 768aee5e960..e6357c96bcd 100644 --- a/js/initcontext_test.go +++ b/js/initcontext_test.go @@ -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) { diff --git a/js/internal/modules/modules.go b/js/internal/modules/modules.go new file mode 100644 index 00000000000..058fe459ae0 --- /dev/null +++ b/js/internal/modules/modules.go @@ -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 . + * + */ + +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 +} diff --git a/js/modules.go b/js/modules.go new file mode 100644 index 00000000000..28d780b0940 --- /dev/null +++ b/js/modules.go @@ -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 . + * + */ + +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" +) diff --git a/js/modules/index.go b/js/modules/index.go deleted file mode 100644 index 981f68b9e22..00000000000 --- a/js/modules/index.go +++ /dev/null @@ -1,46 +0,0 @@ -/* - * - * k6 - a next-generation load testing tool - * Copyright (C) 2016 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 . - * - */ - -package modules - -import ( - "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/html" - "github.com/loadimpact/k6/js/modules/k6/http" - "github.com/loadimpact/k6/js/modules/k6/metrics" - "github.com/loadimpact/k6/js/modules/k6/ws" -) - -// Index of module implementations. -var Index = map[string]interface{}{ - "k6": k6.New(), - "k6/crypto": crypto.New(), - "k6/crypto/x509": x509.New(), - "k6/encoding": encoding.New(), - "k6/http": http.New(), - "k6/metrics": metrics.New(), - "k6/html": html.New(), - "k6/ws": ws.New(), - "k6/protocols/grpc": grpc.New(), -} diff --git a/js/modules/k6/crypto/crypto.go b/js/modules/k6/crypto/crypto.go index 354ea16f99d..ae3f8418de8 100644 --- a/js/modules/k6/crypto/crypto.go +++ b/js/modules/k6/crypto/crypto.go @@ -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 { diff --git a/js/modules/k6/crypto/x509/x509.go b/js/modules/k6/crypto/x509/x509.go index 28480bd6aa5..cb3201faae0 100644 --- a/js/modules/k6/crypto/x509/x509.go +++ b/js/modules/k6/crypto/x509/x509.go @@ -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{} diff --git a/js/modules/k6/encoding/encoding.go b/js/modules/k6/encoding/encoding.go index a8a417fcc68..2309eef6886 100644 --- a/js/modules/k6/encoding/encoding.go +++ b/js/modules/k6/encoding/encoding.go @@ -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 { diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index 99039fd85ce..bf23679629c 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -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 @@ -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 diff --git a/js/modules/k6/grpc/grpc.go b/js/modules/k6/grpc/grpc.go index 83e41bfaff8..829e2e50e6a 100644 --- a/js/modules/k6/grpc/grpc.go +++ b/js/modules/k6/grpc/grpc.go @@ -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"` diff --git a/js/modules/k6/html/html.go b/js/modules/k6/html/html.go index f5efc2e11af..61c597771f2 100644 --- a/js/modules/k6/html/html.go +++ b/js/modules/k6/html/html.go @@ -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 { diff --git a/js/modules/k6/http/http.go b/js/modules/k6/http/http.go index 3b7ea5b17fe..8aa6cdab2dc 100644 --- a/js/modules/k6/http/http.go +++ b/js/modules/k6/http/http.go @@ -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" diff --git a/js/modules/k6/k6.go b/js/modules/k6/k6.go index 7bbfddd5e94..32a2cafe58b 100644 --- a/js/modules/k6/k6.go +++ b/js/modules/k6/k6.go @@ -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 diff --git a/js/modules/k6/metrics/metrics.go b/js/modules/k6/metrics/metrics.go index 437b9bc0189..9f3d3cc491e 100644 --- a/js/modules/k6/metrics/metrics.go +++ b/js/modules/k6/metrics/metrics.go @@ -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) diff --git a/js/modules/k6/ws/ws.go b/js/modules/k6/ws/ws.go index 97da421ceaa..09bb9af4d8d 100644 --- a/js/modules/k6/ws/ws.go +++ b/js/modules/k6/ws/ws.go @@ -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") diff --git a/js/modules/modules.go b/js/modules/modules.go new file mode 100644 index 00000000000..fa7ebdd9fa6 --- /dev/null +++ b/js/modules/modules.go @@ -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 . + * + */ + +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) +}