Skip to content

Commit

Permalink
Merge branch 'master' into feature/diable-setup-with-timeout-0
Browse files Browse the repository at this point in the history
  • Loading branch information
tsukasaI authored Aug 31, 2024
2 parents c258fb8 + b3f18b2 commit 600661b
Show file tree
Hide file tree
Showing 81 changed files with 2,121 additions and 795 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/issue-auto-assign.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- uses: actions/github-script@v7
with:
script: |
const assignees = ['mstoykov', 'codebien', 'olegbespalov', 'oleiade', 'joanlopez'];
const assignees = ['mstoykov', 'olegbespalov', 'oleiade', 'joanlopez'];
const assigneeCount = 1;
// Do not automatically assign users if someone was already assigned or it was opened by a maintainer
Expand Down
9 changes: 7 additions & 2 deletions cmd/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"sync"
"time"

"github.com/fatih/color"
"go.k6.io/k6/cloudapi"
"go.k6.io/k6/cmd/state"
"go.k6.io/k6/errext"
Expand All @@ -21,6 +20,7 @@ import (
"go.k6.io/k6/lib/consts"
"go.k6.io/k6/ui/pb"

"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
Expand Down Expand Up @@ -317,7 +317,8 @@ func (c *cmdCloud) run(cmd *cobra.Command, args []string) error {
// Although by looking at [ResultStatus] and [RunStatus] isn't self-explanatory,
// the scenario when the test run has finished, but it failed is an exceptional case for those situations
// when thresholds have been crossed (failed). So, we report this situation as such.
if testProgress.RunStatus == cloudapi.RunStatusFinished {
if testProgress.RunStatus == cloudapi.RunStatusFinished ||
testProgress.RunStatus == cloudapi.RunStatusAbortedThreshold {
return errext.WithExitCodeIfNone(errors.New("Thresholds have been crossed"), exitcodes.ThresholdsHaveFailed)
}

Expand All @@ -341,6 +342,9 @@ func (c *cmdCloud) flagSet() *pflag.FlagSet {
"enable showing of logs when a test is executed in the cloud")
flags.BoolVar(&c.uploadOnly, "upload-only", c.uploadOnly,
"only upload the test to the cloud without actually starting a test run")
if err := flags.MarkDeprecated("upload-only", "use \"k6 cloud upload\" instead"); err != nil {
panic(err) // Should never happen
}

return flags
}
Expand Down Expand Up @@ -389,6 +393,7 @@ service. Be sure to run the "k6 cloud login" command prior to authenticate with
// Register `k6 cloud` subcommands
cloudCmd.AddCommand(getCmdCloudRun(gs))
cloudCmd.AddCommand(getCmdCloudLogin(gs))
cloudCmd.AddCommand(getCmdCloudUpload(c))

cloudCmd.Flags().SortFlags = false
cloudCmd.Flags().AddFlagSet(c.flagSet())
Expand Down
8 changes: 4 additions & 4 deletions cmd/cloud_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ func getCmdCloudLogin(gs *state.GlobalState) *cobra.Command {
// loginCloudCommand represents the 'cloud login' command
exampleText := getExampleText(gs, `
# Prompt for a Grafana Cloud k6 token
{{.}} cloud login
$ {{.}} cloud login
# Store a token in k6's persistent configuration
{{.}} cloud login -t <YOUR_TOKEN>
$ {{.}} cloud login -t <YOUR_TOKEN>
# Display the stored token
{{.}} cloud login -s
$ {{.}} cloud login -s
# Reset the stored token
{{.}} cloud login -r`[1:])
$ {{.}} cloud login -r`[1:])

loginCloudCommand := &cobra.Command{
Use: cloudLoginCommandName,
Expand Down
68 changes: 68 additions & 0 deletions cmd/cloud_upload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package cmd

import (
"go.k6.io/k6/cmd/state"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

const cloudUploadCommandName = "upload"

type cmdCloudUpload struct {
globalState *state.GlobalState

// deprecatedCloudCmd holds an instance of the k6 cloud command that we store
// in order to be able to call its run method to support the cloud upload
// feature
deprecatedCloudCmd *cmdCloud
}

func getCmdCloudUpload(cloudCmd *cmdCloud) *cobra.Command {
c := &cmdCloudUpload{
globalState: cloudCmd.gs,
deprecatedCloudCmd: cloudCmd,
}

// uploadCloudCommand represents the 'cloud upload' command
exampleText := getExampleText(cloudCmd.gs, `
# Upload the test script and its resources to the Grafana Cloud k6 without actually starting a test run
$ {{.}} cloud upload script.js`[1:])

uploadCloudCommand := &cobra.Command{
Use: cloudUploadCommandName,
Short: "Upload the test script to the Grafana Cloud k6",
Long: `Upload the test script and its resources to the Grafana Cloud k6.
This will upload the test script and its resources to the Grafana Cloud k6 service.
Using this command requires to be authenticated against the Grafana Cloud k6.
Use the "k6 cloud login" command to authenticate.
`,
Example: exampleText,
Args: exactArgsWithMsg(1, "arg should either be \"-\", if reading script from stdin, or a path to a script file"),
PreRunE: c.preRun,
RunE: c.run,
}

uploadCloudCommand.Flags().AddFlagSet(c.flagSet())

return uploadCloudCommand
}

func (c *cmdCloudUpload) preRun(cmd *cobra.Command, args []string) error {
return c.deprecatedCloudCmd.preRun(cmd, args)
}

// run is the code that runs when the user executes `k6 cloud upload`
func (c *cmdCloudUpload) run(cmd *cobra.Command, args []string) error {
c.deprecatedCloudCmd.uploadOnly = true
return c.deprecatedCloudCmd.run(cmd, args)
}

func (c *cmdCloudUpload) flagSet() *pflag.FlagSet {
flags := pflag.NewFlagSet("", pflag.ContinueOnError)
flags.SortFlags = false
flags.AddFlagSet(optionFlagSet())
flags.AddFlagSet(runtimeOptionFlagSet(false))
return flags
}
21 changes: 21 additions & 0 deletions cmd/tests/cmd_cloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,27 @@ func runCloudTests(t *testing.T, setupCmd setupCommandFunc) {
t.Log(stdout)
assert.Contains(t, stdout, `Thresholds have been crossed`)
})

t.Run("TestCloudAbortedThreshold", func(t *testing.T) {
t.Parallel()

progressCallback := func() cloudapi.TestProgressResponse {
return cloudapi.TestProgressResponse{
RunStatusText: "Finished",
RunStatus: cloudapi.RunStatusAbortedThreshold,
ResultStatus: cloudapi.ResultStatusFailed,
Progress: 1.0,
}
}
ts := getSimpleCloudTestState(t, nil, setupCmd, nil, nil, progressCallback)
ts.ExpectedExitCode = int(exitcodes.ThresholdsHaveFailed)

cmd.ExecuteWithGlobalState(ts.GlobalState)

stdout := ts.Stdout.String()
t.Log(stdout)
assert.Contains(t, stdout, `Thresholds have been crossed`)
})
}

func cloudTestStartSimple(tb testing.TB, testRunID int) http.Handler {
Expand Down
157 changes: 157 additions & 0 deletions cmd/tests/cmd_cloud_upload_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package tests

import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"testing"

"go.k6.io/k6/cloudapi"
"go.k6.io/k6/cmd"
"go.k6.io/k6/lib/fsext"
"go.k6.io/k6/lib/testutils"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestK6CloudUpload(t *testing.T) {
t.Parallel()

t.Run("TestCloudUploadNotLoggedIn", func(t *testing.T) {
t.Parallel()

ts := getSimpleCloudTestState(t, nil, setupK6CloudUploadCmd, nil, nil, nil)
delete(ts.Env, "K6_CLOUD_TOKEN")
ts.ExpectedExitCode = -1
cmd.ExecuteWithGlobalState(ts.GlobalState)

stdout := ts.Stdout.String()
t.Log(stdout)
assert.Contains(t, stdout, `not logged in`)
})

t.Run("TestCloudUploadWithScript", func(t *testing.T) {
t.Parallel()

cs := func() cloudapi.TestProgressResponse {
return cloudapi.TestProgressResponse{
RunStatusText: "Archived",
RunStatus: cloudapi.RunStatusArchived,
}
}

ts := getSimpleCloudTestState(t, nil, setupK6CloudUploadCmd, nil, nil, cs)
cmd.ExecuteWithGlobalState(ts.GlobalState)

stdout := ts.Stdout.String()
t.Log(stdout)
assert.Contains(t, stdout, `execution: cloud`)
assert.Contains(t, stdout, `output: https://app.k6.io/runs/123`)
assert.Contains(t, stdout, `test status: Archived`)
})

// TestCloudUploadWithArchive tests that if k6 uses a static archive with the script inside that has cloud options like:
//
// export let options = {
// ext: {
// loadimpact: {
// name: "my load test",
// projectID: 124,
// note: "lorem ipsum",
// },
// }
// };
//
// actually sends to the cloud the archive with the correct metadata (metadata.json), like:
//
// "ext": {
// "loadimpact": {
// "name": "my load test",
// "note": "lorem ipsum",
// "projectID": 124
// }
// }
t.Run("TestCloudUploadWithArchive", func(t *testing.T) {
t.Parallel()

testRunID := 123
ts := NewGlobalTestState(t)

archiveUpload := http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
// check the archive
file, _, err := req.FormFile("file")
assert.NoError(t, err)
assert.NotNil(t, file)

// temporary write the archive for file system
data, err := io.ReadAll(file)
assert.NoError(t, err)

tmpPath := filepath.Join(ts.Cwd, "archive_to_cloud.tar")
require.NoError(t, fsext.WriteFile(ts.FS, tmpPath, data, 0o644))

// check what inside
require.NoError(t, testutils.Untar(t, ts.FS, tmpPath, "tmp/"))

metadataRaw, err := fsext.ReadFile(ts.FS, "tmp/metadata.json")
require.NoError(t, err)

metadata := struct {
Options struct {
Cloud struct {
Name string `json:"name"`
Note string `json:"note"`
ProjectID int `json:"projectID"`
} `json:"cloud"`
} `json:"options"`
}{}

// then unpacked metadata should not contain any environment variables passed at the moment of archive creation
require.NoError(t, json.Unmarshal(metadataRaw, &metadata))
require.Equal(t, "my load test", metadata.Options.Cloud.Name)
require.Equal(t, "lorem ipsum", metadata.Options.Cloud.Note)
require.Equal(t, 124, metadata.Options.Cloud.ProjectID)

// respond with the test run ID
resp.WriteHeader(http.StatusOK)
_, err = fmt.Fprintf(resp, `{"reference_id": "%d"}`, testRunID)
assert.NoError(t, err)
})

cs := func() cloudapi.TestProgressResponse {
return cloudapi.TestProgressResponse{
RunStatusText: "Archived",
RunStatus: cloudapi.RunStatusArchived,
}
}

srv := getMockCloud(t, testRunID, archiveUpload, cs)

data, err := os.ReadFile(filepath.Join("testdata/archives", "archive_v0.46.0_with_loadimpact_option.tar")) //nolint:forbidigo // it's a test
require.NoError(t, err)

require.NoError(t, fsext.WriteFile(ts.FS, filepath.Join(ts.Cwd, "archive.tar"), data, 0o644))

ts.CmdArgs = []string{"k6", "cloud", "upload", "archive.tar"}
ts.Env["K6_SHOW_CLOUD_LOGS"] = "false" // no mock for the logs yet
ts.Env["K6_CLOUD_HOST"] = srv.URL
ts.Env["K6_CLOUD_TOKEN"] = "foo" // doesn't matter, we mock the cloud

cmd.ExecuteWithGlobalState(ts.GlobalState)

stdout := ts.Stdout.String()
t.Log(stdout)
assert.NotContains(t, stdout, `not logged in`)
assert.Contains(t, stdout, `execution: cloud`)
assert.Contains(t, stdout, `output: https://app.k6.io/runs/123`)
assert.Contains(t, stdout, `test status: Archived`)
})
}

func setupK6CloudUploadCmd(cliFlags []string) []string {
return append([]string{"k6", "cloud", "upload"}, append(cliFlags, "test.js")...)
}
15 changes: 3 additions & 12 deletions cmd/tests/cmd_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"path/filepath"
"regexp"
"runtime"
"slices"
"strings"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -1434,16 +1435,6 @@ func sum(vals []float64) (sum float64) {
return sum
}

func maxFloat64(vals []float64) float64 {
result := vals[0]
for _, val := range vals {
if result < val {
result = val
}
}
return result
}

func TestActiveVUsCount(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -1499,8 +1490,8 @@ func TestActiveVUsCount(t *testing.T) {
jsonResults, err := fsext.ReadFile(ts.FS, "results.json")
require.NoError(t, err)
// t.Log(string(jsonResults))
assert.Equal(t, float64(10), maxFloat64(getSampleValues(t, jsonResults, "vus_max", nil)))
assert.Equal(t, float64(10), maxFloat64(getSampleValues(t, jsonResults, "vus", nil)))
assert.Equal(t, float64(10), slices.Max(getSampleValues(t, jsonResults, "vus_max", nil)))
assert.Equal(t, float64(10), slices.Max(getSampleValues(t, jsonResults, "vus", nil)))
assert.Equal(t, float64(0), sum(getSampleValues(t, jsonResults, "iterations", nil)))

logEntries := ts.LoggerHook.Drain()
Expand Down
4 changes: 2 additions & 2 deletions cmd/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,10 @@ func showProgress(ctx context.Context, gs *state.GlobalState, pbs []*pb.Progress
var leftLen int64
for _, pb := range pbs {
l := pb.Left()
leftLen = lib.Max(int64(len(l)), leftLen)
leftLen = max(int64(len(l)), leftLen)
}
// Limit to maximum left text length
maxLeft := int(lib.Min(leftLen, maxLeftLength))
maxLeft := int(min(leftLen, maxLeftLength))

var progressBarsLastRenderLock sync.Mutex
var progressBarsLastRender []byte
Expand Down
Loading

0 comments on commit 600661b

Please sign in to comment.