## Description This PR introduces the `cmdtest` package, offering a lightweight wrapper around cobra commands to simplify testing CLI utilities. I backfilled tests for the `version` command, which was an example of a very simple test setup; and for the `export` command, which was more involved due to the server and client context requirements. I did notice that there are some existing tests for some utilities, but the `cmdtest` package follows a simple pattern that has been easy to use successfully in [the relayer](https://github.com/cosmos/relayer/blob/main/internal/relayertest/system.go) and in other projects outside the Cosmos ecosystem. While filling in these tests, I started removing uses of `cmd.Print`, as that is the root cause of issues like #8498, #7964, #15167, and possibly others. Internal to cobra, the print family of methods write to `cmd.OutOrStderr()` -- meaning that if the authors call `cmd.SetOutput()` before executing the command, the output will be written to stdout as expected; otherwise it will go to stderr. I don't understand why that would be the default behavior, but it is probably too late to change from cobra's side. Instead of `cmd.Print`, we prefer to `fmt.Fprint(cmd.OutOrStdout())` or `fmt.Fprint(cmd.ErrOrStderr())` as appropriate, giving an unambiguous destination for output. And the new tests collect those outputs in plain `bytes.Buffer` values so that we can assert their content appropriately. In the longer term, I would like to deprecate and eventually remove the `testutil` package's `ApplyMockIO` method and its `BufferWriter` and `BufferReader` types, as they are unnecessary indirection when a simpler solution exists. But that can wait until `cmdtest` has propagated through the codebase more. --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#pr-targeting)) - [ ] ~~provided a link to the relevant issue or specification~~ - [x] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed all author checklist items have been addressed - [ ] confirmed that this PR does not change production code
165 lines
4.2 KiB
Go
165 lines
4.2 KiB
Go
package version_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/cometbft/cometbft/libs/cli"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/cosmos/cosmos-sdk/testutil"
|
|
"github.com/cosmos/cosmos-sdk/testutil/cmdtest"
|
|
"github.com/cosmos/cosmos-sdk/version"
|
|
)
|
|
|
|
func TestNewInfo(t *testing.T) {
|
|
info := version.NewInfo()
|
|
want := fmt.Sprintf(`:
|
|
git commit:
|
|
build tags:
|
|
%s`, fmt.Sprintf("go version %s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH))
|
|
require.Equal(t, want, info.String())
|
|
}
|
|
|
|
func TestInfo_String(t *testing.T) {
|
|
info := version.Info{
|
|
Name: "testapp",
|
|
AppName: "testappd",
|
|
Version: "1.0.0",
|
|
GitCommit: "1b78457135a4104bc3af97f20654d49e2ea87454",
|
|
BuildTags: "netgo,ledger",
|
|
GoVersion: "go version go1.14 linux/amd64",
|
|
CosmosSdkVersion: "0.42.5",
|
|
}
|
|
want := `testapp: 1.0.0
|
|
git commit: 1b78457135a4104bc3af97f20654d49e2ea87454
|
|
build tags: netgo,ledger
|
|
go version go1.14 linux/amd64`
|
|
require.Equal(t, want, info.String())
|
|
}
|
|
|
|
func TestCLI(t *testing.T) {
|
|
setVersionPackageVars(t)
|
|
|
|
sys := cmdtest.NewSystem()
|
|
sys.AddCommands(version.NewVersionCommand())
|
|
|
|
t.Run("no flags", func(t *testing.T) {
|
|
res := sys.MustRun(t, "version")
|
|
|
|
// Only prints the version, with a newline, to stdout.
|
|
require.Equal(t, testVersion+"\n", res.Stdout.String())
|
|
require.Empty(t, res.Stderr.String())
|
|
})
|
|
|
|
t.Run("--long flag", func(t *testing.T) {
|
|
res := sys.MustRun(t, "version", "--long")
|
|
|
|
out := res.Stdout.String()
|
|
lines := strings.Split(out, "\n")
|
|
require.Contains(t, lines, "name: testchain-app")
|
|
require.Contains(t, lines, "server_name: testchaind")
|
|
require.Contains(t, lines, `version: "3.14"`)
|
|
require.Contains(t, lines, "commit: abc123")
|
|
require.Contains(t, lines, "build_tags: mybuildtag")
|
|
|
|
require.Empty(t, res.Stderr.String())
|
|
})
|
|
|
|
t.Run("--output=json flag", func(t *testing.T) {
|
|
res := sys.MustRun(t, "version", "--output=json")
|
|
|
|
var info version.Info
|
|
require.NoError(t, json.Unmarshal(res.Stdout.Bytes(), &info))
|
|
|
|
// Assert against a couple fields that are difficult to predict in test
|
|
// without copying and pasting code.
|
|
require.NotEmpty(t, info.GoVersion)
|
|
|
|
// The SDK version appears to not be set during this test, so we'll ignore it here.
|
|
|
|
// Now clear out the non-empty fields, so we can compare against a fixed value.
|
|
info.GoVersion = ""
|
|
|
|
want := version.Info{
|
|
Name: testName,
|
|
AppName: testAppName,
|
|
Version: testVersion,
|
|
GitCommit: testCommit,
|
|
BuildTags: testBuildTags,
|
|
}
|
|
require.Equal(t, want, info)
|
|
|
|
require.Empty(t, res.Stderr.String())
|
|
})
|
|
|
|
t.Run("positional args rejected", func(t *testing.T) {
|
|
res := sys.Run("version", "foo")
|
|
require.Error(t, res.Err)
|
|
})
|
|
}
|
|
|
|
const (
|
|
testName = "testchain-app"
|
|
testAppName = "testchaind"
|
|
testVersion = "3.14"
|
|
testCommit = "abc123"
|
|
testBuildTags = "mybuildtag"
|
|
)
|
|
|
|
// setVersionPackageVars temporarily overrides the package variables in the version package
|
|
// so that we can assert meaningful output.
|
|
func setVersionPackageVars(t *testing.T) {
|
|
t.Helper()
|
|
|
|
var (
|
|
origName = version.Name
|
|
origAppName = version.AppName
|
|
origVersion = version.Version
|
|
origCommit = version.Commit
|
|
origBuildTags = version.BuildTags
|
|
)
|
|
|
|
t.Cleanup(func() {
|
|
version.Name = origName
|
|
version.AppName = origAppName
|
|
version.Version = origVersion
|
|
version.Commit = origCommit
|
|
version.BuildTags = origBuildTags
|
|
})
|
|
|
|
version.Name = testName
|
|
version.AppName = testAppName
|
|
version.Version = testVersion
|
|
version.Commit = testCommit
|
|
version.BuildTags = testBuildTags
|
|
}
|
|
|
|
func Test_runVersionCmd(t *testing.T) {
|
|
cmd := version.NewVersionCommand()
|
|
_, mockOut := testutil.ApplyMockIO(cmd)
|
|
|
|
cmd.SetArgs([]string{
|
|
fmt.Sprintf("--%s=''", cli.OutputFlag),
|
|
"--long=false",
|
|
})
|
|
|
|
require.NoError(t, cmd.Execute())
|
|
assert.Equal(t, "\n", mockOut.String())
|
|
mockOut.Reset()
|
|
|
|
cmd.SetArgs([]string{
|
|
fmt.Sprintf("--%s=json", cli.OutputFlag), "--long=true",
|
|
})
|
|
|
|
info := version.NewInfo()
|
|
stringInfo, err := json.Marshal(info)
|
|
require.NoError(t, err)
|
|
require.NoError(t, cmd.Execute())
|
|
assert.Equal(t, string(stringInfo)+"\n", mockOut.String())
|
|
}
|