diff --git a/cmd/cloud_run.go b/cmd/cloud_run.go index 866a5b94af8..744c4803325 100644 --- a/cmd/cloud_run.go +++ b/cmd/cloud_run.go @@ -131,25 +131,27 @@ func (c *cmdCloudRun) preRun(cmd *cobra.Command, args []string) error { func (c *cmdCloudRun) run(cmd *cobra.Command, args []string) error { if c.localExecution { - // We know this execution requires a test run to be created in the Cloud. - // So, we create it before delegating the actual execution to the run command. - // To do that, we need to load the test and configure it. - test, err := loadAndConfigureLocalTest(c.runCmd.gs, cmd, args, getCloudRunLocalExecutionConfig) - if err != nil { - return fmt.Errorf("could not load and configure the test: %w", err) - } - - // As we've already loaded the test, we can modify the init function to - // reuse the initialized one. c.runCmd.loadConfiguredTest = func(*cobra.Command, []string) (*loadedAndConfiguredTest, execution.Controller, error) { - return test, local.NewController(), nil - } + test, err := loadAndConfigureLocalTest(c.runCmd.gs, cmd, args, getCloudRunLocalExecutionConfig) + if err != nil { + return nil, nil, fmt.Errorf("could not load and configure the test: %w", err) + } + + // If the "K6_CLOUDRUN_TEST_RUN_ID" is set, then it means that this code is being executed in the k6 Cloud. + // Therefore, we don't need to continue with the test run creation, as we don't need to create any test run. + // This should technically never happen, as k6, when executed in the Cloud, it uses the standard "run" + // command "locally", but we add this early return just in case, for safety. + // + // If not, we know this execution requires a test run to be created in the Cloud. + // So, we create it as part of the process of loading and configuring the test. + if _, isSet := c.runCmd.gs.Env[testRunIDKey]; !isSet { + if err := createCloudTest(c.runCmd.gs, test); err != nil { + return nil, nil, fmt.Errorf("could not create the cloud test run: %w", err) + } + } - // After that, we can create the remote test run. - if err := createCloudTest(c.runCmd.gs, test); err != nil { - return fmt.Errorf("could not create the cloud test run: %w", err) + return test, local.NewController(), nil } - return c.runCmd.run(cmd, args) } diff --git a/cmd/outputs_cloud.go b/cmd/outputs_cloud.go index f1335dc68e7..d217a82b6c2 100644 --- a/cmd/outputs_cloud.go +++ b/cmd/outputs_cloud.go @@ -25,20 +25,13 @@ const ( ) // createCloudTest performs some test and Cloud configuration validations and if everything -// looks good, then it creates a test run in the k6 Cloud, unless k6 is already running in the Cloud. -// It is also responsible for filling the test run id on the test options, so it can be used later. -// It returns the resulting Cloud configuration as a json.RawMessage, as expected by the Cloud output, -// or an error if something goes wrong. +// looks good, then it creates a test run in the k6 Cloud, using the Cloud API, meant to be used +// for streaming test results. +// +// This method is also responsible for filling the test run id on the test environment, so it can be used later, +// and to populate the Cloud configuration back in case the Cloud API returned some overrides, +// as expected by the Cloud output. func createCloudTest(gs *state.GlobalState, test *loadedAndConfiguredTest) error { - // If the "K6_CLOUDRUN_TEST_RUN_ID" is set, then it means that this code is being executed in the k6 Cloud. - // Therefore, we don't need to continue with the test run creation, as we don't need to create any test run. - // - // This should technically never happen, as k6, when executed in the Cloud, it uses the standard "run" - // command "locally", but we add this early return just in case, for safety. - if _, isSet := gs.Env[testRunIDKey]; isSet { - return nil - } - // Otherwise, we continue normally with the creation of the test run in the k6 Cloud backend services. conf, warn, err := cloudapi.GetConsolidatedConfig( test.derivedConfig.Collectors[builtinOutputCloud.String()], @@ -128,12 +121,24 @@ func createCloudTest(gs *state.GlobalState, test *loadedAndConfiguredTest) error return err } + // We store the test run id in the environment, so it can be used later. + test.preInitState.RuntimeOptions.Env[testRunIDKey] = response.ReferenceID + + // If the Cloud API returned configuration overrides, we apply them to the current configuration. + // Then, we serialize the overridden configuration back, so it can be used by the Cloud output. if response.ConfigOverride != nil { logger.WithFields(logrus.Fields{"override": response.ConfigOverride}).Debug("overriding config options") - conf = conf.Apply(*response.ConfigOverride) - } - test.preInitState.RuntimeOptions.Env[testRunIDKey] = response.ReferenceID + raw, err := cloudConfToRawMessage(conf.Apply(*response.ConfigOverride)) + if err != nil { + return fmt.Errorf("could not serialize overridden cloud configuration: %w", err) + } + + if test.derivedConfig.Collectors == nil { + test.derivedConfig.Collectors = make(map[string]json.RawMessage) + } + test.derivedConfig.Collectors[builtinOutputCloud.String()] = raw + } return nil } diff --git a/cmd/tests/cmd_cloud_run_test.go b/cmd/tests/cmd_cloud_run_test.go index b15d7269305..c6706a93b92 100644 --- a/cmd/tests/cmd_cloud_run_test.go +++ b/cmd/tests/cmd_cloud_run_test.go @@ -7,16 +7,16 @@ import ( "io" "net/http" "path/filepath" + "strconv" "testing" - "go.k6.io/k6/errext/exitcodes" - "go.k6.io/k6/lib/fsext" - + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.k6.io/k6/cloudapi" - "github.com/stretchr/testify/assert" + "go.k6.io/k6/cloudapi" "go.k6.io/k6/cmd" + "go.k6.io/k6/errext/exitcodes" + "go.k6.io/k6/lib/fsext" ) func TestK6CloudRun(t *testing.T) { @@ -169,6 +169,36 @@ export default function() {};` assert.Contains(t, stdout, "execution: local") assert.Contains(t, stdout, "output: cloud (https://some.other.url/foo/tests/org/1337?bar=baz)") }) + + t.Run("the script can read the test run id to the environment", func(t *testing.T) { + t.Parallel() + + script := ` +export const options = { + cloud: { + name: 'Hello k6 Cloud!', + projectID: 123456, + }, +}; + +export default function() { + ` + "console.log(`The test run id is ${__ENV.K6_CLOUDRUN_TEST_RUN_ID}`);" + ` +};` + + ts := makeTestState(t, script, []string{"--local-execution", "--log-output=stdout"}, 0) + + const testRunID = 1337 + srv := getCloudTestEndChecker(t, testRunID, nil, cloudapi.RunStatusFinished, cloudapi.ResultStatusPassed) + ts.Env["K6_CLOUD_HOST"] = srv.URL + + cmd.ExecuteWithGlobalState(ts.GlobalState) + + stdout := ts.Stdout.String() + t.Log(stdout) + assert.Contains(t, stdout, "execution: local") + assert.Contains(t, stdout, "output: cloud (https://app.k6.io/runs/1337)") + assert.Contains(t, stdout, "The test run id is "+strconv.Itoa(testRunID)) + }) } func makeTestState(tb testing.TB, script string, cliFlags []string, expExitCode exitcodes.ExitCode) *GlobalTestState { diff --git a/output/cloud/output.go b/output/cloud/output.go index 1d2ccbef1d7..c4294c816f4 100644 --- a/output/cloud/output.go +++ b/output/cloud/output.go @@ -21,8 +21,10 @@ import ( "go.k6.io/k6/usage" ) -// TestName is the default k6 Cloud test name -const TestName = "k6 test" +const ( + defaultTestName = "k6 test" + testRunIDKey = "K6_CLOUDRUN_TEST_RUN_ID" +) // versionedOutput represents an output implementing // metrics samples aggregation and flushing to the @@ -120,7 +122,7 @@ func newOutput(params output.Params) (*Output, error) { conf.Name = null.StringFrom(filepath.Base(scriptPath)) } if conf.Name.String == "-" { - conf.Name = null.StringFrom(TestName) + conf.Name = null.StringFrom(defaultTestName) } duration, testEnds := lib.GetEndOffset(params.ExecutionPlan) @@ -148,6 +150,7 @@ func newOutput(params output.Params) (*Output, error) { duration: int64(duration / time.Second), logger: logger, usage: params.Usage, + testRunID: params.RuntimeOptions.Env[testRunIDKey], }, nil }