From a91a30a815790465a19ad222c4ec22c92191c4c5 Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Wed, 15 Apr 2020 20:58:27 -0400 Subject: [PATCH 01/16] load plugins specified by the command line parameter --- cmd/common.go | 8 ++++++++ cmd/options.go | 3 +++ cmd/run.go | 21 +++++++++++++++++---- lib/options.go | 3 +++ lib/plugin.go | 34 ++++++++++++++++++++++++++++++++++ plugin/plugin.go | 40 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 lib/plugin.go create mode 100644 plugin/plugin.go diff --git a/cmd/common.go b/cmd/common.go index df2745f3b13..79468de265f 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -104,6 +104,14 @@ func getNullString(flags *pflag.FlagSet, key string) null.String { return null.NewString(v, flags.Changed(key)) } +func getStringSlice(flags *pflag.FlagSet, key string) []string { + v, err := flags.GetStringSlice(key) + if err != nil { + panic(err) + } + return v +} + func exactArgsWithMsg(n int, msg string) cobra.PositionalArgs { return func(cmd *cobra.Command, args []string) error { if len(args) != n { diff --git a/cmd/options.go b/cmd/options.go index decdfe453d2..b1d11a5e841 100644 --- a/cmd/options.go +++ b/cmd/options.go @@ -82,6 +82,7 @@ func optionFlagSet() *pflag.FlagSet { flags.StringSlice("tag", nil, "add a `tag` to be applied to all samples, as `[name]=[value]`") flags.String("console-output", "", "redirects the console logging to the provided output file") flags.Bool("discard-response-bodies", false, "Read but don't process or save HTTP response bodies") + flags.StringSlice("plugin", []string{}, "load a plugin at `path`") return flags } @@ -110,6 +111,8 @@ func getOptions(flags *pflag.FlagSet) (lib.Options, error) { TeardownTimeout: types.NullDuration{Duration: types.Duration(10 * time.Second), Valid: false}, MetricSamplesBufferSize: null.NewInt(1000, false), + + Plugins: getStringSlice(flags, "plugin"), } // Using Changed() because GetStringSlice() doesn't differentiate between empty and no value diff --git a/cmd/run.go b/cmd/run.go index 27c775e4766..e748ecbfac6 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -49,6 +49,7 @@ import ( "github.com/loadimpact/k6/lib/consts" "github.com/loadimpact/k6/lib/types" "github.com/loadimpact/k6/loader" + "github.com/loadimpact/k6/plugin" "github.com/loadimpact/k6/ui" ) @@ -107,6 +108,22 @@ a commandline interface for interacting with it.`, Left: func() string { return " init" }, } + // Load plugins + cliConf, err := getConfig(cmd.Flags()) + if err != nil { + return err + } + + plugins := []plugin.JavaScriptPlugin{} + for _, pluginPath := range cliConf.Plugins { + jsPlugin, err := lib.LoadPlugin(pluginPath) + if err != nil { + return err + } + + plugins = append(plugins, jsPlugin) + } + // Create the Runner. fprintf(stdout, "%s runner\r", initBar.String()) pwd, err := os.Getwd() @@ -132,10 +149,6 @@ a commandline interface for interacting with it.`, fprintf(stdout, "%s options\r", initBar.String()) - cliConf, err := getConfig(cmd.Flags()) - if err != nil { - return err - } conf, err := getConsolidatedConfig(afero.NewOsFs(), cliConf, r) if err != nil { return err diff --git a/lib/options.go b/lib/options.go index f94341a3ff3..bbcff366216 100644 --- a/lib/options.go +++ b/lib/options.go @@ -285,6 +285,9 @@ type Options struct { // Redirect console logging to a file ConsoleOutput null.String `json:"-" envconfig:"K6_CONSOLE_OUTPUT"` + + // Plugins to load + Plugins []string `json:"plugins" envconfig:"K6_PLUGINS"` } // Returns the result of overwriting any fields with any that are set on the argument. diff --git a/lib/plugin.go b/lib/plugin.go new file mode 100644 index 00000000000..f5b94e61f6c --- /dev/null +++ b/lib/plugin.go @@ -0,0 +1,34 @@ +package lib + +import ( + "fmt" + goplugin "plugin" + + "github.com/loadimpact/k6/plugin" +) + +func LoadJavaScriptPlugin(path string) (plugin.JavaScriptPlugin, error) { + p, err := loadPlugin(path) + if err != nil { + return nil, err + } + + jsSym, err := p.Lookup("JavaScriptPlugin") + if err != nil { + return nil, err + } + + var jsPlugin plugin.JavaScriptPlugin + jsPlugin, ok := jsSym.(plugin.JavaScriptPlugin) + if !ok { + return nil, fmt.Errorf("could not cast plugin to the right type") + } + + return jsPlugin, nil +} + +func loadPlugin(path string) (*goplugin.Plugin, error) { + // TODO: check for things like the file existing and return nicer errors + p, err := goplugin.Open(path) + return p, err +} diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 00000000000..35085e1aeae --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,40 @@ +/* + * + * 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 plugin + +// A Plugin ... +type Plugin interface { + // Returns a user-friendly name for the plugin. + Name() string + + // Gets the plugin in a state where it can be utilized in a load balancing test. + Setup() error + + // Cleans up any state / files that need cleaning right before k6 shuts down. + Teardown() error +} + +type JavaScriptPlugin interface { + Plugin + + // Returns a list of modules that this plugin exposes. + GetModules() map[string]interface{} +} From f6d5963d2ada9432377b6c1f0e641ee4a5b9b202 Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Wed, 15 Apr 2020 21:01:18 -0400 Subject: [PATCH 02/16] fix plugin load invocation --- cmd/run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/run.go b/cmd/run.go index e748ecbfac6..ec3145b3173 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -116,7 +116,7 @@ a commandline interface for interacting with it.`, plugins := []plugin.JavaScriptPlugin{} for _, pluginPath := range cliConf.Plugins { - jsPlugin, err := lib.LoadPlugin(pluginPath) + jsPlugin, err := lib.LoadJavaScriptPlugin(pluginPath) if err != nil { return err } From f1c38d9df7c1aee9c7c29a8cef6d4664bdb6564c Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Wed, 15 Apr 2020 21:25:57 -0400 Subject: [PATCH 03/16] add plugin to modules; look plugins up on require; teardown plugins on run end --- cmd/run.go | 19 +++++++++++++++++++ js/initcontext.go | 16 +++++++++++++++- js/modules/index.go | 3 +++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/cmd/run.go b/cmd/run.go index ec3145b3173..ba905d7a608 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -45,6 +45,7 @@ import ( "github.com/loadimpact/k6/core" "github.com/loadimpact/k6/core/local" "github.com/loadimpact/k6/js" + "github.com/loadimpact/k6/js/modules" "github.com/loadimpact/k6/lib" "github.com/loadimpact/k6/lib/consts" "github.com/loadimpact/k6/lib/types" @@ -121,6 +122,18 @@ a commandline interface for interacting with it.`, return err } + // Run plugin setup and add it to the module tree + if err = jsPlugin.Setup(); err != nil { + return err + } + + // TODO: does this belong here, or is it the module package's responsibility? + mods := jsPlugin.GetModules() + for path, api := range mods { + // TODO: check if module already exists and if we're not overloading it + modules.PluginIndex[path] = api + } + plugins = append(plugins, jsPlugin) } @@ -486,6 +499,12 @@ a commandline interface for interacting with it.`, } } + // Teardown plugins + for _, jsPlugin := range plugins { + // TODO: does it really matter if teardown errors? + jsPlugin.Teardown() + } + if conf.Linger.Bool { logrus.Info("Linger set; waiting for Ctrl+C...") <-sigC diff --git a/js/initcontext.go b/js/initcontext.go index f78620e099c..5f558027efa 100644 --- a/js/initcontext.go +++ b/js/initcontext.go @@ -113,8 +113,14 @@ func (i *InitContext) Require(arg string) goja.Value { } return v default: + // Try in the plugin store + v, err := i.requirePluginModule(arg) + if err == nil { + return v + } + // Fall back to loading from the filesystem. - v, err := i.requireFile(arg) + v, err = i.requireFile(arg) if err != nil { common.Throw(i.runtime, err) } @@ -122,6 +128,14 @@ func (i *InitContext) Require(arg string) goja.Value { } } +func (i *InitContext) requirePluginModule(name string) (goja.Value, error) { + mod, ok := modules.PluginIndex[name] + if !ok { + return nil, errors.Errorf("unknown plugin module: %s", name) + } + return i.runtime.ToValue(common.Bind(i.runtime, mod, i.ctxPtr)), nil +} + func (i *InitContext) requireModule(name string) (goja.Value, error) { mod, ok := modules.Index[name] if !ok { diff --git a/js/modules/index.go b/js/modules/index.go index 7e6d15d2cd9..4529695aa45 100644 --- a/js/modules/index.go +++ b/js/modules/index.go @@ -42,3 +42,6 @@ var Index = map[string]interface{}{ "k6/html": html.New(), "k6/ws": ws.New(), } + +// Index of plugin module implementations. +var PluginIndex = map[string]interface{}{} From fb2703d2cf818441468cebf2848901356e50082a Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Thu, 16 Apr 2020 20:45:08 -0400 Subject: [PATCH 04/16] wrap up plugin support; add tests --- .circleci/config.yml | 7 +++++++ Makefile | 6 +++++- cmd/run.go | 14 +++++--------- js/initcontext.go | 11 ++++++----- js/initcontext_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ js/modules/index.go | 13 ++++++++++++- lib/options_test.go | 5 +++++ lib/plugin.go | 6 +++++- plugin/plugin.go | 3 ++- 9 files changed, 87 insertions(+), 18 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f13908db9bd..642ebe4fc4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,6 +64,13 @@ jobs: working_directory: /home/circleci/.go_workspace/src/github.com/loadimpact/k6 steps: - checkout + - run: + name: Build plugin artifact for tests + command: | + go version + pushd /tmp + go build -buildmode=plugin -o /tmp/leftpad.so github.com/andremedeiros/leftpad + popd - run: name: Run tests and code coverage command: | diff --git a/Makefile b/Makefile index c10bd275222..9d145d6eedb 100644 --- a/Makefile +++ b/Makefile @@ -10,8 +10,12 @@ build: format: find . -name '*.go' -exec gofmt -s -w {} + +.PHONY: plugin +plugin: + pushd /tmp; go build -buildmode=plugin -o /tmp/leftpad.so github.com/andremedeiros/leftpad; popd + .PHONY: check -check: +check: plugin golangci-lint run --out-format=tab --new-from-rev master ./... go test -race -timeout 210s ./... diff --git a/cmd/run.go b/cmd/run.go index ba905d7a608..29c6a1fb1d9 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -116,6 +116,7 @@ a commandline interface for interacting with it.`, } plugins := []plugin.JavaScriptPlugin{} + pluginNames := []string{} for _, pluginPath := range cliConf.Plugins { jsPlugin, err := lib.LoadJavaScriptPlugin(pluginPath) if err != nil { @@ -127,14 +128,9 @@ a commandline interface for interacting with it.`, return err } - // TODO: does this belong here, or is it the module package's responsibility? - mods := jsPlugin.GetModules() - for path, api := range mods { - // TODO: check if module already exists and if we're not overloading it - modules.PluginIndex[path] = api - } - + modules.RegisterPluginModules(jsPlugin.GetModules()) plugins = append(plugins, jsPlugin) + pluginNames = append(pluginNames, jsPlugin.Name()) } // Create the Runner. @@ -275,6 +271,7 @@ a commandline interface for interacting with it.`, } fprintf(stdout, " execution: %s\n", ui.ValueColor.Sprint("local")) + fprintf(stdout, " plugins: %s\n", ui.ValueColor.Sprint(strings.Join(pluginNames, ", "))) fprintf(stdout, " output: %s%s\n", ui.ValueColor.Sprint(out), ui.ExtraColor.Sprint(link)) fprintf(stdout, " script: %s\n", ui.ValueColor.Sprint(filename)) fprintf(stdout, "\n") @@ -501,8 +498,7 @@ a commandline interface for interacting with it.`, // Teardown plugins for _, jsPlugin := range plugins { - // TODO: does it really matter if teardown errors? - jsPlugin.Teardown() + _ = jsPlugin.Teardown() } if conf.Linger.Bool { diff --git a/js/initcontext.go b/js/initcontext.go index 5f558027efa..e81fb75f02e 100644 --- a/js/initcontext.go +++ b/js/initcontext.go @@ -112,15 +112,16 @@ func (i *InitContext) Require(arg string) goja.Value { common.Throw(i.runtime, err) } return v - default: + case strings.HasPrefix(arg, "k6-plugin/"): // Try in the plugin store v, err := i.requirePluginModule(arg) - if err == nil { - return v + if err != nil { + common.Throw(i.runtime, err) } - + return v + default: // Fall back to loading from the filesystem. - v, err = i.requireFile(arg) + v, err := i.requireFile(arg) if err != nil { common.Throw(i.runtime, err) } diff --git a/js/initcontext_test.go b/js/initcontext_test.go index a2b805c2454..8a6cd04e0d5 100644 --- a/js/initcontext_test.go +++ b/js/initcontext_test.go @@ -39,6 +39,7 @@ import ( "github.com/stretchr/testify/require" "github.com/loadimpact/k6/js/common" + "github.com/loadimpact/k6/js/modules" "github.com/loadimpact/k6/lib" "github.com/loadimpact/k6/lib/netext" "github.com/loadimpact/k6/stats" @@ -233,6 +234,45 @@ func TestInitContextRequire(t *testing.T) { assert.NoError(t, err) }) }) + + t.Run("Plugins", func(t *testing.T) { + t.Run("leftpad", func(t *testing.T) { + plugin, err := lib.LoadJavaScriptPlugin("/tmp/leftpad.so") + assert.NoError(t, err) + assert.Equal(t, "Leftpad", plugin.Name()) + + modules.RegisterPluginModules(plugin.GetModules()) + + b, err := getSimpleBundle("/script.js", ` + import { leftpad } from "k6-plugin/leftpad"; + export default function() { + return leftpad('test', 10, '='); + } + `) + if !assert.NoError(t, err, "bundle error") { + return + } + + bi, err := b.Instantiate() + if !assert.NoError(t, err, "instance error") { + return + } + + val, err := bi.Default(goja.Undefined()) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, "======test", val.Export()) + }) + t.Run("Nonexistent", func(t *testing.T) { + _, err := getSimpleBundle("/script.js", ` + import { leftpad } from "k6-plugin/nonexistent"; + export default function() {} + `) + require.Error(t, err) + assert.Contains(t, err.Error(), "unknown plugin module: k6-plugin/nonexistent") + }) + }) } func createAndReadFile(t *testing.T, file string, content []byte, expectedLength int, binary bool) (*BundleInstance, error) { diff --git a/js/modules/index.go b/js/modules/index.go index 4529695aa45..1ddeb008005 100644 --- a/js/modules/index.go +++ b/js/modules/index.go @@ -21,6 +21,8 @@ package modules import ( + "fmt" + "github.com/loadimpact/k6/js/modules/k6" "github.com/loadimpact/k6/js/modules/k6/crypto" "github.com/loadimpact/k6/js/modules/k6/crypto/x509" @@ -43,5 +45,14 @@ var Index = map[string]interface{}{ "k6/ws": ws.New(), } -// Index of plugin module implementations. +// PluginIndex holds a map of plugin modules to their respective implementations. var PluginIndex = map[string]interface{}{} + +// RegisterPluginModules takes care of registering a map of paths that a plugin exposes so they can be +// loaded from the JavaScript VM. +func RegisterPluginModules(modules map[string]interface{}) { + for path, impl := range modules { + importPath := fmt.Sprintf("k6-plugin/%s", path) + PluginIndex[importPath] = impl + } +} diff --git a/lib/options_test.go b/lib/options_test.go index c7d86928636..28c5368a161 100644 --- a/lib/options_test.go +++ b/lib/options_test.go @@ -476,6 +476,11 @@ func TestOptionsEnv(t *testing.T) { "true": null.BoolFrom(true), "false": null.BoolFrom(false), }, + {"Plugins", "K6_PLUGINS"}: { + "": []string{}, + "foo.so": []string{"foo.so"}, + "foo.so,bar.so": []string{"foo.so", "bar.so"}, + }, // Thresholds // External } diff --git a/lib/plugin.go b/lib/plugin.go index f5b94e61f6c..e46e2f7c749 100644 --- a/lib/plugin.go +++ b/lib/plugin.go @@ -7,6 +7,8 @@ import ( "github.com/loadimpact/k6/plugin" ) +// LoadJavaScriptPlugin tries to load a dynamic library that should conform to +// the `plug.JavaScriptPlugin` interface. func LoadJavaScriptPlugin(path string) (plugin.JavaScriptPlugin, error) { p, err := loadPlugin(path) if err != nil { @@ -28,7 +30,9 @@ func LoadJavaScriptPlugin(path string) (plugin.JavaScriptPlugin, error) { } func loadPlugin(path string) (*goplugin.Plugin, error) { - // TODO: check for things like the file existing and return nicer errors p, err := goplugin.Open(path) + if err != nil { + err = fmt.Errorf("error while loading plugin: %w", err) + } return p, err } diff --git a/plugin/plugin.go b/plugin/plugin.go index 35085e1aeae..1d43ae0be7b 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -20,7 +20,7 @@ package plugin -// A Plugin ... +// A Plugin is used to extend the javascript VM with functionality implemented in Golang. type Plugin interface { // Returns a user-friendly name for the plugin. Name() string @@ -32,6 +32,7 @@ type Plugin interface { Teardown() error } +// A JavaScriptPlugin represents a binding between code that can run in the JavaScript VM and Golang. type JavaScriptPlugin interface { Plugin From 4b984b5449eef8b471b30d84f2ae52e830043359 Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Thu, 16 Apr 2020 20:47:03 -0400 Subject: [PATCH 05/16] add copyright header to lib/plugin.go --- lib/plugin.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/plugin.go b/lib/plugin.go index e46e2f7c749..0833bb00cec 100644 --- a/lib/plugin.go +++ b/lib/plugin.go @@ -1,3 +1,23 @@ +/* + * + * 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 lib import ( From 86aeafa3337b62c6bd5d3942a662aed4665f993d Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Thu, 16 Apr 2020 20:53:20 -0400 Subject: [PATCH 06/16] ci fix --- .circleci/config.yml | 1 + Makefile | 1 + 2 files changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 642ebe4fc4c..e929e4aeb5a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -69,6 +69,7 @@ jobs: command: | go version pushd /tmp + go get -d github.com/andremedeiros/leftpad go build -buildmode=plugin -o /tmp/leftpad.so github.com/andremedeiros/leftpad popd - run: diff --git a/Makefile b/Makefile index 9d145d6eedb..d7c2f26b650 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ format: .PHONY: plugin plugin: + pushd /tmp; go get -d github.com/andremedeiros/leftpad; popd pushd /tmp; go build -buildmode=plugin -o /tmp/leftpad.so github.com/andremedeiros/leftpad; popd .PHONY: check From 946f88cbb3ce684bef089fd8c0d69c70f587a8af Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Thu, 16 Apr 2020 21:04:38 -0400 Subject: [PATCH 07/16] another attempt at fixing ci --- .circleci/config.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e929e4aeb5a..9e3fa3e66c7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,12 +66,13 @@ jobs: - checkout - run: name: Build plugin artifact for tests + working_directory: /tmp command: | go version - pushd /tmp + export GOMAXPROCS=2 + export PATH=$GOPATH/bin:$PATH go get -d github.com/andremedeiros/leftpad go build -buildmode=plugin -o /tmp/leftpad.so github.com/andremedeiros/leftpad - popd - run: name: Run tests and code coverage command: | @@ -100,6 +101,15 @@ jobs: working_directory: /home/circleci/.go_workspace/src/github.com/loadimpact/k6 steps: - checkout + - run: + name: Build plugin artifact for tests + working_directory: /tmp + command: | + go version + export GOMAXPROCS=2 + export PATH=$GOPATH/bin:$PATH + go get -d github.com/andremedeiros/leftpad + go build -buildmode=plugin -o /tmp/leftpad.so github.com/andremedeiros/leftpad - run: name: Run tests with previous Go version command: | From a82e894d0d26b2eaf90cd1d046d95362d00d11cd Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Thu, 16 Apr 2020 21:10:25 -0400 Subject: [PATCH 08/16] fix test assertion --- js/initcontext_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/initcontext_test.go b/js/initcontext_test.go index 8a6cd04e0d5..c5d63651a22 100644 --- a/js/initcontext_test.go +++ b/js/initcontext_test.go @@ -238,7 +238,9 @@ func TestInitContextRequire(t *testing.T) { t.Run("Plugins", func(t *testing.T) { t.Run("leftpad", func(t *testing.T) { plugin, err := lib.LoadJavaScriptPlugin("/tmp/leftpad.so") - assert.NoError(t, err) + if !assert.NoError(t, err) { + return + } assert.Equal(t, "Leftpad", plugin.Name()) modules.RegisterPluginModules(plugin.GetModules()) From af0ae4a4f6bb52879bfb8ff39c1d826e25e69eca Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Sat, 9 May 2020 17:09:35 -0400 Subject: [PATCH 09/16] revert circleci changes --- .circleci/config.yml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9e3fa3e66c7..f13908db9bd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,15 +64,6 @@ jobs: working_directory: /home/circleci/.go_workspace/src/github.com/loadimpact/k6 steps: - checkout - - run: - name: Build plugin artifact for tests - working_directory: /tmp - command: | - go version - export GOMAXPROCS=2 - export PATH=$GOPATH/bin:$PATH - go get -d github.com/andremedeiros/leftpad - go build -buildmode=plugin -o /tmp/leftpad.so github.com/andremedeiros/leftpad - run: name: Run tests and code coverage command: | @@ -101,15 +92,6 @@ jobs: working_directory: /home/circleci/.go_workspace/src/github.com/loadimpact/k6 steps: - checkout - - run: - name: Build plugin artifact for tests - working_directory: /tmp - command: | - go version - export GOMAXPROCS=2 - export PATH=$GOPATH/bin:$PATH - go get -d github.com/andremedeiros/leftpad - go build -buildmode=plugin -o /tmp/leftpad.so github.com/andremedeiros/leftpad - run: name: Run tests with previous Go version command: | From dcc2a42c2d29a47bc1970669eac6f5286ebbd183 Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Sat, 9 May 2020 17:09:46 -0400 Subject: [PATCH 10/16] revert makefile changes --- Makefile | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Makefile b/Makefile index d7c2f26b650..c10bd275222 100644 --- a/Makefile +++ b/Makefile @@ -10,13 +10,8 @@ build: format: find . -name '*.go' -exec gofmt -s -w {} + -.PHONY: plugin -plugin: - pushd /tmp; go get -d github.com/andremedeiros/leftpad; popd - pushd /tmp; go build -buildmode=plugin -o /tmp/leftpad.so github.com/andremedeiros/leftpad; popd - .PHONY: check -check: plugin +check: golangci-lint run --out-format=tab --new-from-rev master ./... go test -race -timeout 210s ./... From aecb2812e6a7aaf69d149fc3be9c02e1371ca8fd Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Sat, 9 May 2020 17:09:57 -0400 Subject: [PATCH 11/16] shuffle some code around --- js/initcontext_test.go | 23 +++++++++++++++++++- lib/plugin_windows.go | 31 +++++++++++++++++++++++++++ samples/leftpad/leftpad.go | 44 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 lib/plugin_windows.go create mode 100644 samples/leftpad/leftpad.go diff --git a/js/initcontext_test.go b/js/initcontext_test.go index c5d63651a22..126f0dafb31 100644 --- a/js/initcontext_test.go +++ b/js/initcontext_test.go @@ -27,7 +27,10 @@ import ( "net" "net/http" "net/http/httptest" + "os" + "os/exec" "path/filepath" + "runtime" "testing" "time" @@ -236,8 +239,26 @@ func TestInitContextRequire(t *testing.T) { }) t.Run("Plugins", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test due to lack of plugin support in windows") + return + } + t.Run("leftpad", func(t *testing.T) { - plugin, err := lib.LoadJavaScriptPlugin("/tmp/leftpad.so") + tf, err := ioutil.TempFile("", "leftpad.so") + if !assert.NoError(t, err, "temporary file error") { + return + } + defer os.Remove(tf.Name()) + + // go build -buildmode=plugin -o /tmp/leftpad.so + cmd := exec.Command("go", "build", "-buildmode=plugin", "-o", tf.Name(), "github.com/loadimpact/k6/samples/leftpad") + err = cmd.Run() + if !assert.NoError(t, err, "compiling sample plugin error") { + return + } + + plugin, err := lib.LoadJavaScriptPlugin(tf.Name()) if !assert.NoError(t, err) { return } diff --git a/lib/plugin_windows.go b/lib/plugin_windows.go new file mode 100644 index 00000000000..d3f01fe2eba --- /dev/null +++ b/lib/plugin_windows.go @@ -0,0 +1,31 @@ +/* + * + * 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 lib + +import ( + "github.com/loadimpact/k6/plugin" + "github.com/pkg/errors" +) + +// LoadJavaScriptPlugin does not work on Windows, so it always returns an error. +func LoadJavaScriptPlugin(path string) (plugin.JavaScriptPlugin, error) { + return nil, errors.New("plugins are not supported in Windows") +} diff --git a/samples/leftpad/leftpad.go b/samples/leftpad/leftpad.go new file mode 100644 index 00000000000..156589d61d5 --- /dev/null +++ b/samples/leftpad/leftpad.go @@ -0,0 +1,44 @@ +package main + +import "strings" + +type leftpad struct{} + +func New() *leftpad { + return &leftpad{} +} + +func (*leftpad) Leftpad(s string, l int, padding string) string { + if len(s) >= l { + return s + } + + return strings.Repeat(padding, l-len(s)) + s +} + +type plugin struct{} + +var JavaScriptPlugin plugin + +func (*plugin) Name() string { + return "Leftpad" +} + +func (*plugin) Setup() error { + return nil +} + +func (*plugin) Teardown() error { + return nil +} + +func (*plugin) GetModules() map[string]interface{} { + mods := map[string]interface{}{ + "leftpad": New(), + } + return mods +} + +func init() { + JavaScriptPlugin = plugin{} +} From 4f36ff091aeace03897aaa65cd55e397176eb913 Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Sat, 9 May 2020 17:35:22 -0400 Subject: [PATCH 12/16] run js setup in a parallel group --- Gopkg.lock | 9 +++ cmd/run.go | 22 +++++-- samples/leftpad/leftpad.go | 7 +- vendor/golang.org/x/sync/AUTHORS | 3 + vendor/golang.org/x/sync/CONTRIBUTORS | 3 + vendor/golang.org/x/sync/LICENSE | 27 ++++++++ vendor/golang.org/x/sync/PATENTS | 22 +++++++ vendor/golang.org/x/sync/errgroup/errgroup.go | 66 +++++++++++++++++++ 8 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 vendor/golang.org/x/sync/AUTHORS create mode 100644 vendor/golang.org/x/sync/CONTRIBUTORS create mode 100644 vendor/golang.org/x/sync/LICENSE create mode 100644 vendor/golang.org/x/sync/PATENTS create mode 100644 vendor/golang.org/x/sync/errgroup/errgroup.go diff --git a/Gopkg.lock b/Gopkg.lock index 2243c01a8ac..e5294c818e1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -604,6 +604,14 @@ pruneopts = "NUT" revision = "351d144fa1fc0bd934e2408202be0c29f25e35a0" +[[projects]] + branch = "master" + digest = "1:b521f10a2d8fa85c04a8ef4e62f2d1e14d303599a55d64dabf9f5a02f84d35eb" + name = "golang.org/x/sync" + packages = ["errgroup"] + pruneopts = "NUT" + revision = "43a5402ce75a95522677f77c619865d66b8c57ab" + [[projects]] branch = "master" digest = "1:b1a955501003b967a2c814e41ded50c7b29dfdeb7f266b7987b8ed38f65205d6" @@ -722,6 +730,7 @@ "golang.org/x/crypto/ssh/terminal", "golang.org/x/net/html", "golang.org/x/net/http2", + "golang.org/x/sync/errgroup", "golang.org/x/text/unicode/norm", "golang.org/x/time/rate", "gopkg.in/guregu/null.v3", diff --git a/cmd/run.go b/cmd/run.go index 29c6a1fb1d9..75f40c52e6a 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -39,6 +39,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/spf13/pflag" + "golang.org/x/sync/errgroup" null "gopkg.in/guregu/null.v3" "github.com/loadimpact/k6/api" @@ -115,24 +116,31 @@ a commandline interface for interacting with it.`, return err } + g, _ := errgroup.WithContext(context.Background()) + if err != nil { + return err + } + plugins := []plugin.JavaScriptPlugin{} pluginNames := []string{} for _, pluginPath := range cliConf.Plugins { - jsPlugin, err := lib.LoadJavaScriptPlugin(pluginPath) - if err != nil { - return err + jsPlugin, pluginErr := lib.LoadJavaScriptPlugin(pluginPath) + if pluginErr != nil { + return pluginErr } - // Run plugin setup and add it to the module tree - if err = jsPlugin.Setup(); err != nil { - return err - } + // Do the part that actually takes time in a runner group. + g.Go(jsPlugin.Setup) modules.RegisterPluginModules(jsPlugin.GetModules()) plugins = append(plugins, jsPlugin) pluginNames = append(pluginNames, jsPlugin.Name()) } + if err = g.Wait(); err != nil { + return err + } + // Create the Runner. fprintf(stdout, "%s runner\r", initBar.String()) pwd, err := os.Getwd() diff --git a/samples/leftpad/leftpad.go b/samples/leftpad/leftpad.go index 156589d61d5..48e5470874f 100644 --- a/samples/leftpad/leftpad.go +++ b/samples/leftpad/leftpad.go @@ -4,10 +4,6 @@ import "strings" type leftpad struct{} -func New() *leftpad { - return &leftpad{} -} - func (*leftpad) Leftpad(s string, l int, padding string) string { if len(s) >= l { return s @@ -34,11 +30,12 @@ func (*plugin) Teardown() error { func (*plugin) GetModules() map[string]interface{} { mods := map[string]interface{}{ - "leftpad": New(), + "leftpad": &leftpad{}, } return mods } func init() { JavaScriptPlugin = plugin{} + _ = JavaScriptPlugin // shut up, linter... } diff --git a/vendor/golang.org/x/sync/AUTHORS b/vendor/golang.org/x/sync/AUTHORS new file mode 100644 index 00000000000..15167cd746c --- /dev/null +++ b/vendor/golang.org/x/sync/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/sync/CONTRIBUTORS b/vendor/golang.org/x/sync/CONTRIBUTORS new file mode 100644 index 00000000000..1c4577e9680 --- /dev/null +++ b/vendor/golang.org/x/sync/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/sync/LICENSE b/vendor/golang.org/x/sync/LICENSE new file mode 100644 index 00000000000..6a66aea5eaf --- /dev/null +++ b/vendor/golang.org/x/sync/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/sync/PATENTS b/vendor/golang.org/x/sync/PATENTS new file mode 100644 index 00000000000..733099041f8 --- /dev/null +++ b/vendor/golang.org/x/sync/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/sync/errgroup/errgroup.go b/vendor/golang.org/x/sync/errgroup/errgroup.go new file mode 100644 index 00000000000..9857fe53d3c --- /dev/null +++ b/vendor/golang.org/x/sync/errgroup/errgroup.go @@ -0,0 +1,66 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package errgroup provides synchronization, error propagation, and Context +// cancelation for groups of goroutines working on subtasks of a common task. +package errgroup + +import ( + "context" + "sync" +) + +// A Group is a collection of goroutines working on subtasks that are part of +// the same overall task. +// +// A zero Group is valid and does not cancel on error. +type Group struct { + cancel func() + + wg sync.WaitGroup + + errOnce sync.Once + err error +} + +// WithContext returns a new Group and an associated Context derived from ctx. +// +// The derived Context is canceled the first time a function passed to Go +// returns a non-nil error or the first time Wait returns, whichever occurs +// first. +func WithContext(ctx context.Context) (*Group, context.Context) { + ctx, cancel := context.WithCancel(ctx) + return &Group{cancel: cancel}, ctx +} + +// Wait blocks until all function calls from the Go method have returned, then +// returns the first non-nil error (if any) from them. +func (g *Group) Wait() error { + g.wg.Wait() + if g.cancel != nil { + g.cancel() + } + return g.err +} + +// Go calls the given function in a new goroutine. +// +// The first call to return a non-nil error cancels the group; its error will be +// returned by Wait. +func (g *Group) Go(f func() error) { + g.wg.Add(1) + + go func() { + defer g.wg.Done() + + if err := f(); err != nil { + g.errOnce.Do(func() { + g.err = err + if g.cancel != nil { + g.cancel() + } + }) + } + }() +} From bea978a97088902c5221cb0c009ed5cc7cfcc2d9 Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Sat, 9 May 2020 17:44:17 -0400 Subject: [PATCH 13/16] shuffle test helper around some --- js/initcontext_test.go | 26 +++++++++++++++----------- js/modules/index.go | 2 +- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/js/initcontext_test.go b/js/initcontext_test.go index 126f0dafb31..462931ae5c8 100644 --- a/js/initcontext_test.go +++ b/js/initcontext_test.go @@ -30,7 +30,6 @@ import ( "os" "os/exec" "path/filepath" - "runtime" "testing" "time" @@ -48,6 +47,15 @@ import ( "github.com/loadimpact/k6/stats" ) +func getTemporaryFile(name string) (string, func()) { + tf, _ := ioutil.TempFile("", name) + cleanup := func() { + _ = os.Remove(tf.Name()) + } + + return tf.Name(), cleanup +} + func TestInitContextRequire(t *testing.T) { t.Run("Modules", func(t *testing.T) { t.Run("Nonexistent", func(t *testing.T) { @@ -239,26 +247,22 @@ func TestInitContextRequire(t *testing.T) { }) t.Run("Plugins", func(t *testing.T) { - if runtime.GOOS == "windows" { + if isWindows { t.Skip("skipping test due to lack of plugin support in windows") return } t.Run("leftpad", func(t *testing.T) { - tf, err := ioutil.TempFile("", "leftpad.so") - if !assert.NoError(t, err, "temporary file error") { - return - } - defer os.Remove(tf.Name()) + path, cleanup := getTemporaryFile("leftpad.so") + defer cleanup() // go build -buildmode=plugin -o /tmp/leftpad.so - cmd := exec.Command("go", "build", "-buildmode=plugin", "-o", tf.Name(), "github.com/loadimpact/k6/samples/leftpad") - err = cmd.Run() - if !assert.NoError(t, err, "compiling sample plugin error") { + cmd := exec.Command("go", "build", "-buildmode=plugin", "-o", path, "github.com/loadimpact/k6/samples/leftpad") + if err := cmd.Run(); !assert.NoError(t, err, "compiling sample plugin error") { return } - plugin, err := lib.LoadJavaScriptPlugin(tf.Name()) + plugin, err := lib.LoadJavaScriptPlugin(path) if !assert.NoError(t, err) { return } diff --git a/js/modules/index.go b/js/modules/index.go index 1ddeb008005..6327e522615 100644 --- a/js/modules/index.go +++ b/js/modules/index.go @@ -45,7 +45,7 @@ var Index = map[string]interface{}{ "k6/ws": ws.New(), } -// PluginIndex holds a map of plugin modules to their respective implementations. +// PluginIndex holds an index of plugin module implementations. var PluginIndex = map[string]interface{}{} // RegisterPluginModules takes care of registering a map of paths that a plugin exposes so they can be From b93869324ff0f4ed1af80303de236c39bc109628 Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Sat, 9 May 2020 17:49:43 -0400 Subject: [PATCH 14/16] add build flag to plugin file --- lib/plugin.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugin.go b/lib/plugin.go index 0833bb00cec..22e4e013821 100644 --- a/lib/plugin.go +++ b/lib/plugin.go @@ -1,3 +1,4 @@ +// +build !windows /* * * k6 - a next-generation load testing tool From a43fe5dd8933b0542d8ad101b9254ea7e41c5cc9 Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Sat, 9 May 2020 17:57:13 -0400 Subject: [PATCH 15/16] turns out file suffixes dont work as i thought --- lib/plugin.go | 7 ++++++- lib/plugin_windows.go | 31 ------------------------------- 2 files changed, 6 insertions(+), 32 deletions(-) delete mode 100644 lib/plugin_windows.go diff --git a/lib/plugin.go b/lib/plugin.go index 22e4e013821..5d40fc091fb 100644 --- a/lib/plugin.go +++ b/lib/plugin.go @@ -1,4 +1,3 @@ -// +build !windows /* * * k6 - a next-generation load testing tool @@ -24,8 +23,10 @@ package lib import ( "fmt" goplugin "plugin" + "runtime" "github.com/loadimpact/k6/plugin" + "github.com/pkg/errors" ) // LoadJavaScriptPlugin tries to load a dynamic library that should conform to @@ -51,6 +52,10 @@ func LoadJavaScriptPlugin(path string) (plugin.JavaScriptPlugin, error) { } func loadPlugin(path string) (*goplugin.Plugin, error) { + if runtime.GOOS == "windows" { + return nil, errors.New("plugins are not supported in Windows") + } + p, err := goplugin.Open(path) if err != nil { err = fmt.Errorf("error while loading plugin: %w", err) diff --git a/lib/plugin_windows.go b/lib/plugin_windows.go deleted file mode 100644 index d3f01fe2eba..00000000000 --- a/lib/plugin_windows.go +++ /dev/null @@ -1,31 +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 lib - -import ( - "github.com/loadimpact/k6/plugin" - "github.com/pkg/errors" -) - -// LoadJavaScriptPlugin does not work on Windows, so it always returns an error. -func LoadJavaScriptPlugin(path string) (plugin.JavaScriptPlugin, error) { - return nil, errors.New("plugins are not supported in Windows") -} From 9dbc2f9ff3d7dbceeb6f06c58b81dcfda3fb3243 Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Sat, 9 May 2020 19:49:19 -0400 Subject: [PATCH 16/16] add race detector so plugin matches platform --- js/initcontext_test.go | 9 +++++++-- testhelpers/israce/race.go | 7 +++++++ testhelpers/israce/race0.go | 7 +++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 testhelpers/israce/race.go create mode 100644 testhelpers/israce/race0.go diff --git a/js/initcontext_test.go b/js/initcontext_test.go index 462931ae5c8..c7895b94b5b 100644 --- a/js/initcontext_test.go +++ b/js/initcontext_test.go @@ -45,6 +45,7 @@ import ( "github.com/loadimpact/k6/lib" "github.com/loadimpact/k6/lib/netext" "github.com/loadimpact/k6/stats" + "github.com/loadimpact/k6/testhelpers/israce" ) func getTemporaryFile(name string) (string, func()) { @@ -256,8 +257,12 @@ func TestInitContextRequire(t *testing.T) { path, cleanup := getTemporaryFile("leftpad.so") defer cleanup() - // go build -buildmode=plugin -o /tmp/leftpad.so - cmd := exec.Command("go", "build", "-buildmode=plugin", "-o", path, "github.com/loadimpact/k6/samples/leftpad") + args := []string{"build", "-buildmode=plugin", "-o", path} + if israce.Enabled { + args = append(args, "-race") + } + args = append(args, "github.com/loadimpact/k6/samples/leftpad") + cmd := exec.Command("go", args...) if err := cmd.Run(); !assert.NoError(t, err, "compiling sample plugin error") { return } diff --git a/testhelpers/israce/race.go b/testhelpers/israce/race.go new file mode 100644 index 00000000000..498981efef3 --- /dev/null +++ b/testhelpers/israce/race.go @@ -0,0 +1,7 @@ +// +build race + +// Package israce reports if the Go race detector is enabled. +package israce + +// Enabled reports if the race detector is enabled. +const Enabled = true diff --git a/testhelpers/israce/race0.go b/testhelpers/israce/race0.go new file mode 100644 index 00000000000..570dc13a282 --- /dev/null +++ b/testhelpers/israce/race0.go @@ -0,0 +1,7 @@ +// +build !race + +// Package israce reports if the Go race detector is enabled. +package israce + +// Enabled reports if the race detector is enabled. +const Enabled = false