Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add basic CLI tests using Go Test #9489

Merged
merged 1 commit into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
linters:
enable:
- stylecheck

linters-settings:
stylecheck:
dot-import-whitelist:
- github.com/ipfs/kubo/test/cli/testutils
2 changes: 1 addition & 1 deletion coverage/Rules.mk
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ include mk/header.mk

GOCC ?= go

$(d)/coverage_deps: $$(DEPS_GO)
$(d)/coverage_deps: $$(DEPS_GO) cmd/ipfs/ipfs
rm -rf $(@D)/unitcover && mkdir $(@D)/unitcover
rm -rf $(@D)/sharnesscover && mkdir $(@D)/sharnesscover

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ require (
go.uber.org/fx v1.18.2
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.3.0
golang.org/x/mod v0.7.0
golang.org/x/sync v0.1.0
golang.org/x/sys v0.3.0
)
Expand Down Expand Up @@ -233,7 +234,6 @@ require (
go.uber.org/multierr v1.8.0 // indirect
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.3.0 // indirect
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
golang.org/x/term v0.3.0 // indirect
Expand Down
238 changes: 238 additions & 0 deletions test/cli/basic_commands_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
package cli

import (
"fmt"
"regexp"
"strings"
"testing"

"github.com/blang/semver/v4"
"github.com/ipfs/kubo/test/cli/harness"
. "github.com/ipfs/kubo/test/cli/testutils"
"github.com/stretchr/testify/assert"
gomod "golang.org/x/mod/module"
)

var versionRegexp = regexp.MustCompile(`^ipfs version (.+)$`)

func parseVersionOutput(s string) semver.Version {
versString := versionRegexp.FindStringSubmatch(s)[1]
v, err := semver.Parse(versString)
if err != nil {
panic(err)
}
return v
}

func TestCurDirIsWritable(t *testing.T) {
t.Parallel()
h := harness.NewT(t)
h.WriteFile("test.txt", "It works!")
}

func TestIPFSVersionCommandMatchesFlag(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode()
commandVersionStr := node.IPFS("version").Stdout.String()
commandVersionStr = strings.TrimSpace(commandVersionStr)
commandVersion := parseVersionOutput(commandVersionStr)

flagVersionStr := node.IPFS("--version").Stdout.String()
flagVersionStr = strings.TrimSpace(flagVersionStr)
flagVersion := parseVersionOutput(flagVersionStr)

assert.Equal(t, commandVersion, flagVersion)
}

func TestIPFSVersionAll(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode()
res := node.IPFS("version", "--all").Stdout.String()
res = strings.TrimSpace(res)
assert.Contains(t, res, "Kubo version")
assert.Contains(t, res, "Repo version")
assert.Contains(t, res, "System version")
assert.Contains(t, res, "Golang version")
}

func TestIPFSVersionDeps(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode()
res := node.IPFS("version", "deps").Stdout.String()
res = strings.TrimSpace(res)
lines := SplitLines(res)

assert.Equal(t, "github.com/ipfs/kubo@(devel)", lines[0])

for _, depLine := range lines[1:] {
split := strings.Split(depLine, " => ")
for _, moduleVersion := range split {
splitModVers := strings.Split(moduleVersion, "@")
modPath := splitModVers[0]
modVers := splitModVers[1]
assert.NoError(t, gomod.Check(modPath, modVers), "path: %s, version: %s", modPath, modVers)
}
}
}

func TestIPFSCommands(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode()
cmds := node.IPFSCommands()
assert.Contains(t, cmds, "ipfs add")
assert.Contains(t, cmds, "ipfs daemon")
assert.Contains(t, cmds, "ipfs update")
}

func TestAllSubcommandsAcceptHelp(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode()
for _, cmd := range node.IPFSCommands() {
t.Run(fmt.Sprintf("command %q accepts help", cmd), func(t *testing.T) {
t.Parallel()
splitCmd := strings.Split(cmd, " ")[1:]
node.IPFS(StrCat("help", splitCmd)...)
node.IPFS(StrCat(splitCmd, "--help")...)
})
}
}

func TestAllRootCommandsAreMentionedInHelpText(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode()
cmds := node.IPFSCommands()
var rootCmds []string
for _, cmd := range cmds {
splitCmd := strings.Split(cmd, " ")
if len(splitCmd) == 2 {
rootCmds = append(rootCmds, splitCmd[1])
}
}

// a few base commands are not expected to be in the help message
// but we default to requiring them to be in the help message, so that we
// have to make an conscious decision to exclude them
notInHelp := map[string]bool{
"object": true,
"shutdown": true,
"tar": true,
"urlstore": true,
"dns": true,
}

helpMsg := strings.TrimSpace(node.IPFS("--help").Stdout.String())
for _, rootCmd := range rootCmds {
if _, ok := notInHelp[rootCmd]; ok {
continue
}
assert.Contains(t, helpMsg, fmt.Sprintf(" %s", rootCmd))
}
}

func TestCommandDocsWidth(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode()

// require new commands to explicitly opt in to longer lines
allowList := map[string]bool{
"ipfs add": true,
"ipfs block put": true,
"ipfs daemon": true,
"ipfs config profile": true,
"ipfs pin remote service": true,
"ipfs name pubsub": true,
"ipfs object patch": true,
"ipfs swarm connect": true,
"ipfs p2p forward": true,
"ipfs p2p close": true,
"ipfs swarm disconnect": true,
"ipfs swarm addrs listen": true,
"ipfs dag resolve": true,
"ipfs dag get": true,
"ipfs object stat": true,
"ipfs pin remote add": true,
"ipfs config show": true,
"ipfs config edit": true,
"ipfs pin remote rm": true,
"ipfs pin remote ls": true,
"ipfs pin verify": true,
"ipfs dht get": true,
"ipfs pin remote service add": true,
"ipfs file ls": true,
"ipfs pin update": true,
"ipfs pin rm": true,
"ipfs p2p": true,
"ipfs resolve": true,
"ipfs dag stat": true,
"ipfs name publish": true,
"ipfs object diff": true,
"ipfs object patch add-link": true,
"ipfs name": true,
"ipfs object patch append-data": true,
"ipfs object patch set-data": true,
"ipfs dht put": true,
"ipfs diag profile": true,
"ipfs diag cmds": true,
"ipfs swarm addrs local": true,
"ipfs files ls": true,
"ipfs stats bw": true,
"ipfs urlstore add": true,
"ipfs swarm peers": true,
"ipfs pubsub sub": true,
"ipfs repo fsck": true,
"ipfs files write": true,
"ipfs swarm limit": true,
"ipfs commands completion fish": true,
"ipfs key export": true,
"ipfs routing get": true,
"ipfs refs": true,
"ipfs refs local": true,
"ipfs cid base32": true,
"ipfs pubsub pub": true,
"ipfs repo ls": true,
"ipfs routing put": true,
"ipfs key import": true,
"ipfs swarm peering add": true,
"ipfs swarm peering rm": true,
"ipfs swarm peering ls": true,
"ipfs update": true,
"ipfs swarm stats": true,
}
for _, cmd := range node.IPFSCommands() {
if _, ok := allowList[cmd]; ok {
continue
}
t.Run(fmt.Sprintf("command %q conforms to docs width limit", cmd), func(t *testing.T) {
splitCmd := strings.Split(cmd, " ")
resStr := node.IPFS(StrCat(splitCmd[1:], "--help")...)
res := strings.TrimSpace(resStr.Stdout.String())
for _, line := range SplitLines(res) {
assert.LessOrEqualf(t, len(line), 80, "expected width %d < 80 for %q", len(line), cmd)
}

})
}
}

func TestAllCommandsFailWhenPassedBadFlag(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode()

for _, cmd := range node.IPFSCommands() {
t.Run(fmt.Sprintf("command %q fails when passed a bad flag", cmd), func(t *testing.T) {
splitCmd := strings.Split(cmd, " ")
res := node.RunIPFS(StrCat(splitCmd, "--badflag")...)
assert.Equal(t, 1, res.Cmd.ProcessState.ExitCode())
})
}

}

func TestCommandsFlags(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode()
resStr := node.IPFS("commands", "--flags").Stdout.String()
assert.Contains(t, resStr, "ipfs pin add --recursive / ipfs pin add -r")
assert.Contains(t, resStr, "ipfs id --format / ipfs id -f")
assert.Contains(t, resStr, "ipfs repo gc --quiet / ipfs repo gc -q")
}
31 changes: 31 additions & 0 deletions test/cli/completion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cli

import (
"fmt"
"testing"

"github.com/ipfs/kubo/test/cli/harness"
. "github.com/ipfs/kubo/test/cli/testutils"
"github.com/stretchr/testify/assert"
)

func TestBashCompletion(t *testing.T) {
t.Parallel()
h := harness.NewT(t)
node := h.NewNode()

res := node.IPFS("commands", "completion", "bash")

length := len(res.Stdout.String())
if length < 100 {
t.Fatalf("expected a long Bash completion file, but got one of length %d", length)
}

t.Run("completion file can be loaded in bash", func(t *testing.T) {
RequiresLinux(t)

completionFile := h.WriteToTemp(res.Stdout.String())
res = h.Sh(fmt.Sprintf("source %s && type -t _ipfs", completionFile))
assert.NoError(t, res.Err)
})
}
Loading