-
Notifications
You must be signed in to change notification settings - Fork 174
Expand file tree
/
Copy pathcommand_test.go
More file actions
143 lines (124 loc) · 4 KB
/
command_test.go
File metadata and controls
143 lines (124 loc) · 4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package maincommand
import (
"fmt"
"strings"
"testing"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v3"
)
func isCapitalized(s string) bool {
if len(s) == 0 {
return true
}
first := string([]byte{s[0]})
return first == strings.ToUpper(first)
}
func hasNoTrailingPeriod(s string) bool {
return !strings.HasSuffix(s, ".") && !strings.HasSuffix(s, "!")
}
var shortChecks = map[string]func(string) bool{
"must be capitalized": isCapitalized,
"must not have trailing period": hasNoTrailingPeriod,
}
var longChecks = map[string]func(string) bool{
"must be capitalized": isCapitalized,
"must have trailing period": func(s string) bool { return s == "" || !hasNoTrailingPeriod(s) },
}
var isCapitalizedCheck = map[string]func(string) bool{
"must be capitalized": isCapitalized,
}
func runChecks(message string, checks map[string]func(string) bool) error {
errors := []string{}
for test, check := range checks {
if !check(message) {
errors = append(errors, test)
}
}
if len(errors) > 0 {
return fmt.Errorf("%v", errors)
}
return nil
}
func getCommandPath(command *cobra.Command) string {
// Assume the binary has no spaces in the filepath.
if path := strings.SplitN(command.CommandPath(), " ", 2); len(path) > 1 {
return path[1]
}
return "roxctl"
}
func checkUsageStyle(t *testing.T, command *cobra.Command) {
command.LocalFlags().VisitAll(func(flag *pflag.Flag) {
assert.NoErrorf(t, runChecks(flag.Usage, longChecks),
`"%s --%s" flag usage: %q`, getCommandPath(command), flag.Name, flag.Usage)
})
assert.NoErrorf(t, runChecks(command.Short, shortChecks),
"%q, short usage: %q", getCommandPath(command), command.Short)
var err error
switch {
case command.Use == "doc [man|md|yaml|rest]":
// This command long description ends with a list, hence exception.
err = runChecks(command.Long, isCapitalizedCheck)
case strings.HasPrefix(command.Long, "roxctl "):
// roxctl is not capitalized.
err = runChecks(command.Long, map[string]func(string) bool{
"must have trailing period": func(s string) bool { return !hasNoTrailingPeriod(s) }})
default:
err = runChecks(command.Long, longChecks)
}
assert.NoErrorf(t, err, "%q, long usage: %q", getCommandPath(command), command.Long)
if command.Short == command.Long {
if command.Short == "" {
t.Errorf("no usage provided for command %q", getCommandPath(command))
} else {
t.Errorf("short and long usage strings are equal for %q", getCommandPath(command))
}
}
for _, subcommand := range command.Commands() {
t.Run(getCommandPath(subcommand), func(t *testing.T) {
checkUsageStyle(t, subcommand)
})
}
}
func Test_Commands(t *testing.T) {
cmd := Command()
checkUsageStyle(t, cmd)
}
type cmdNode struct {
Commands map[string]*cmdNode `yaml:"cmd,inline,omitempty"`
LocalFlags []string `yaml:"LOCAL_FLAGS,omitempty"`
PersistentFlags []string `yaml:"PERSISTENT_FLAGS,omitempty"`
InheritedFlags []string `yaml:"INHERITED_FLAGS,omitempty"`
}
func buildCmdTree(c *cobra.Command) *cmdNode {
command := &cmdNode{
Commands: make(map[string]*cmdNode),
}
c.LocalNonPersistentFlags().VisitAll(func(f *pflag.Flag) {
command.LocalFlags = append(command.LocalFlags, f.Name)
})
c.PersistentFlags().VisitAll(func(f *pflag.Flag) {
command.PersistentFlags = append(command.PersistentFlags, f.Name)
})
c.InheritedFlags().VisitAll(func(f *pflag.Flag) {
command.InheritedFlags = append(command.InheritedFlags, f.Name)
})
for _, cmd := range c.Commands() {
command.Commands[cmd.Name()] = buildCmdTree(cmd)
}
return command
}
func Test_commandTree(t *testing.T) {
sb := &strings.Builder{}
e := yaml.NewEncoder(sb)
e.SetIndent(2)
require.NoError(t, e.Encode(buildCmdTree(Command())))
_ = e.Close()
result := sb.String()
// Regenerate the tree:
// os.WriteFile(commandTreeFilename, []byte(result), 0666)
assert.NotEmpty(t, commandTreeFilename, result)
assert.Equal(t, commandTree, result)
}