Skip to content

Commit

Permalink
Move the cloud test run creation into the test configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
joanlopez committed Dec 19, 2024
1 parent 4e16945 commit 893b760
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 41 deletions.
34 changes: 18 additions & 16 deletions cmd/cloud_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
41 changes: 24 additions & 17 deletions cmd/outputs_cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,15 @@ 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.
//
//nolint:funlen
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()],
Expand Down Expand Up @@ -112,7 +107,7 @@ func createCloudTest(gs *state.GlobalState, test *loadedAndConfiguredTest) error
testRun := &cloudapi.TestRun{
Name: conf.Name.String,
ProjectID: conf.ProjectID.Int64,
VUsMax: int64(lib.GetMaxPossibleVUs(executionPlan)),
VUsMax: int64(lib.GetMaxPossibleVUs(executionPlan)), //nolint:gosec
Thresholds: thresholds,
Duration: int64(duration / time.Second),
Archive: testArchive,
Expand All @@ -128,12 +123,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
}
Expand Down
40 changes: 35 additions & 5 deletions cmd/tests/cmd_cloud_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 6 additions & 3 deletions output/cloud/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down

0 comments on commit 893b760

Please sign in to comment.