From dfe15392afaa04748c87779d2392f78e374f8563 Mon Sep 17 00:00:00 2001 From: Oleg Bespalov Date: Thu, 5 Dec 2024 15:24:49 +0100 Subject: [PATCH] Introduces --json flag for k6 version sub-command --- cmd/version.go | 55 ++++++++++++++++++++++++----- cmd/version_test.go | 82 ++++++++++++++++++++++++++++++++++++++++++++ lib/consts/consts.go | 50 ++++++++++++++++++++++++--- 3 files changed, 174 insertions(+), 13 deletions(-) create mode 100644 cmd/version_test.go diff --git a/cmd/version.go b/cmd/version.go index 661ce378fd7..192679f054b 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/json" "fmt" "strings" @@ -24,16 +25,54 @@ func versionString() string { return v } -func getCmdVersion(_ *state.GlobalState) *cobra.Command { - // versionCmd represents the version command. - return &cobra.Command{ +type versionCmd struct { + gs *state.GlobalState + isJSON bool +} + +func (c *versionCmd) run(cmd *cobra.Command, _ []string) error { + if !c.isJSON { + root := cmd.Root() + root.SetArgs([]string{"--version"}) + _ = root.Execute() + return nil + } + + details := consts.VersionDetails() + if exts := ext.GetAll(); len(exts) > 0 { + ext := make([]map[string]string, 0, len(exts)) + for _, e := range exts { + ext = append(ext, map[string]string{ + "name": e.Name, + "type": e.Type.String(), + "version": e.Version, + "path": e.Path, + }) + } + + details["extensions"] = ext + } + + jsonDetails, err := json.Marshal(details) + if err != nil { + return fmt.Errorf("failed produce a JSON version details: %w", err) + } + + _, err = fmt.Fprintln(c.gs.Stdout, string(jsonDetails)) + return err +} + +func getCmdVersion(gs *state.GlobalState) *cobra.Command { + versionCmd := &versionCmd{gs: gs} + + cmd := &cobra.Command{ Use: "version", Short: "Show application version", Long: `Show the application version and exit.`, - Run: func(cmd *cobra.Command, _ []string) { - root := cmd.Root() - root.SetArgs([]string{"--version"}) - _ = root.Execute() - }, + RunE: versionCmd.run, } + + cmd.Flags().BoolVar(&versionCmd.isJSON, "json", false, "if set, output version information will be in JSON format") + + return cmd } diff --git a/cmd/version_test.go b/cmd/version_test.go new file mode 100644 index 00000000000..7f8c0674372 --- /dev/null +++ b/cmd/version_test.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "encoding/json" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "go.k6.io/k6/cmd/tests" + "go.k6.io/k6/lib/consts" +) + +func TestVersionFlag(t *testing.T) { + t.Parallel() + + ts := tests.NewGlobalTestState(t) + ts.ExpectedExitCode = 0 + ts.CmdArgs = []string{"k6", "--version"} + + ExecuteWithGlobalState(ts.GlobalState) + + stdout := ts.Stdout.String() + t.Log(stdout) + assert.NotEmpty(t, stdout) + + // Check that the version/format string is correct + assert.Contains(t, stdout, "k6 v") + assert.Contains(t, stdout, consts.Version) + assert.Contains(t, stdout, runtime.Version()) + assert.Contains(t, stdout, runtime.GOOS) + assert.Contains(t, stdout, runtime.GOARCH) +} + +func TestVersionSubCommand(t *testing.T) { + t.Parallel() + + ts := tests.NewGlobalTestState(t) + ts.ExpectedExitCode = 0 + ts.CmdArgs = []string{"k6", "version"} + + ExecuteWithGlobalState(ts.GlobalState) + + stdout := ts.Stdout.String() + t.Log(stdout) + assert.NotEmpty(t, stdout) + + // Check that the version/format string is correct + assert.Contains(t, stdout, "k6 v") + assert.Contains(t, stdout, consts.Version) + assert.Contains(t, stdout, runtime.Version()) + assert.Contains(t, stdout, runtime.GOOS) + assert.Contains(t, stdout, runtime.GOARCH) +} + +func TestVersionJSONSubCommand(t *testing.T) { + t.Parallel() + + ts := tests.NewGlobalTestState(t) + ts.ExpectedExitCode = 0 + ts.CmdArgs = []string{"k6", "version", "--json"} + + ExecuteWithGlobalState(ts.GlobalState) + + stdout := ts.Stdout.String() + t.Log(stdout) + assert.NotEmpty(t, stdout) + + // try to unmarshal the JSON output + var details map[string]interface{} + err := json.Unmarshal([]byte(stdout), &details) + assert.NoError(t, err) + + // Check that details are correct + assert.Contains(t, details, "version") + assert.Contains(t, details, "go_version") + assert.Contains(t, details, "go_os") + assert.Contains(t, details, "go_arch") + assert.Equal(t, "v"+consts.Version, details["version"]) + assert.Equal(t, runtime.Version(), details["go_version"]) + assert.Equal(t, runtime.GOOS, details["go_os"]) + assert.Equal(t, runtime.GOARCH, details["go_arch"]) +} diff --git a/lib/consts/consts.go b/lib/consts/consts.go index 8cef8076e1d..5dd66fa59e0 100644 --- a/lib/consts/consts.go +++ b/lib/consts/consts.go @@ -11,14 +11,53 @@ import ( // Version contains the current semantic version of k6. const Version = "0.55.0" +const ( + commitKey = "commit" + commitDirtyKey = "commit_dirty" +) + // FullVersion returns the maximally full version and build information for // the currently running k6 executable. func FullVersion() string { - goVersionArch := fmt.Sprintf("%s, %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH) + details := VersionDetails() + + goVersionArch := fmt.Sprintf("%s, %s/%s", details["go_version"], details["go_os"], details["go_arch"]) + + k6version := fmt.Sprintf("%s", details["version"]) + // for the fallback case when the version is not in the expected format + // cobra adds a "v" prefix to the version + k6version = strings.TrimLeft(k6version, "v") + + commit, ok := details[commitKey].(string) + if !ok || commit == "" { + return fmt.Sprintf("%s (%s)", k6version, goVersionArch) + } + + isDirty, ok := details[commitDirtyKey].(bool) + if ok && isDirty { + commit += "-dirty" + } + + return fmt.Sprintf("%s (commit/%s, %s)", k6version, commit, goVersionArch) +} + +// VersionDetails returns the structured details about version +func VersionDetails() map[string]interface{} { + v := Version + if !strings.HasPrefix(v, "v") { + v = "v" + v + } + + details := map[string]interface{}{ + "version": v, + "go_version": runtime.Version(), + "go_os": runtime.GOOS, + "go_arch": runtime.GOARCH, + } buildInfo, ok := debug.ReadBuildInfo() if !ok { - return fmt.Sprintf("%s (%s)", Version, goVersionArch) + return details } var ( @@ -42,14 +81,15 @@ func FullVersion() string { } if commit == "" { - return fmt.Sprintf("%s (%s)", Version, goVersionArch) + return details } + details[commitKey] = commit if dirty { - commit += "-dirty" + details[commitDirtyKey] = true } - return fmt.Sprintf("%s (commit/%s, %s)", Version, commit, goVersionArch) + return details } // Banner returns the ASCII-art banner with the k6 logo