mirror of
https://github.com/spf13/cobra
synced 2025-05-03 11:57:21 +00:00
306 lines
7.9 KiB
Go
306 lines
7.9 KiB
Go
// Copyright 2013-2023 The Cobra Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package cobra
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"text/template"
|
|
)
|
|
|
|
// assertNoErr checks if an error is not nil and logs it as an error if so. This function is typically used in tests to ensure that no errors occur during the execution of test cases. If an error is encountered, it calls t.Error() with the error message, causing the test to fail.
|
|
func assertNoErr(t *testing.T, e error) {
|
|
if e != nil {
|
|
t.Error(e)
|
|
}
|
|
}
|
|
|
|
// TestAddTemplateFunctions tests the AddTemplateFunc and AddTemplateFuncs functions.
|
|
func TestAddTemplateFunctions(t *testing.T) {
|
|
AddTemplateFunc("t", func() bool { return true })
|
|
AddTemplateFuncs(template.FuncMap{
|
|
"f": func() bool { return false },
|
|
"h": func() string { return "Hello," },
|
|
"w": func() string { return "world." }})
|
|
|
|
c := &Command{}
|
|
c.SetUsageTemplate(`{{if t}}{{h}}{{end}}{{if f}}{{h}}{{end}} {{w}}`)
|
|
|
|
const expected = "Hello, world."
|
|
if got := c.UsageString(); got != expected {
|
|
t.Errorf("Expected UsageString: %v\nGot: %v", expected, got)
|
|
}
|
|
}
|
|
|
|
func TestLevenshteinDistance(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
s string
|
|
t string
|
|
ignoreCase bool
|
|
expected int
|
|
}{
|
|
{
|
|
name: "Equal strings (case-sensitive)",
|
|
s: "hello",
|
|
t: "hello",
|
|
ignoreCase: false,
|
|
expected: 0,
|
|
},
|
|
{
|
|
name: "Equal strings (case-insensitive)",
|
|
s: "Hello",
|
|
t: "hello",
|
|
ignoreCase: true,
|
|
expected: 0,
|
|
},
|
|
{
|
|
name: "Different strings (case-sensitive)",
|
|
s: "kitten",
|
|
t: "sitting",
|
|
ignoreCase: false,
|
|
expected: 3,
|
|
},
|
|
{
|
|
name: "Different strings (case-insensitive)",
|
|
s: "Kitten",
|
|
t: "Sitting",
|
|
ignoreCase: true,
|
|
expected: 3,
|
|
},
|
|
{
|
|
name: "Empty strings",
|
|
s: "",
|
|
t: "",
|
|
ignoreCase: false,
|
|
expected: 0,
|
|
},
|
|
{
|
|
name: "One empty string",
|
|
s: "abc",
|
|
t: "",
|
|
ignoreCase: false,
|
|
expected: 3,
|
|
},
|
|
{
|
|
name: "Both empty strings",
|
|
s: "",
|
|
t: "",
|
|
ignoreCase: true,
|
|
expected: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Act
|
|
got := ld(tt.s, tt.t, tt.ignoreCase)
|
|
|
|
// Assert
|
|
if got != tt.expected {
|
|
t.Errorf("Expected ld: %v\nGot: %v", tt.expected, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestStringInSlice tests the stringInSlice function with various scenarios to ensure its correctness.
|
|
//
|
|
// The test cases cover:
|
|
// - A case-sensitive match where the target string is in the slice.
|
|
// - A case-sensitive match where the target string is not in the slice.
|
|
// - A case-insensitive match, which should return false as the function is case-sensitive.
|
|
// - An empty slice, expecting false.
|
|
// - An empty string, expecting false unless the slice also contains an empty string.
|
|
// - An empty string matching another empty string, expecting true.
|
|
// - An empty string in an empty slice, expecting false.
|
|
func TestStringInSlice(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
a string
|
|
list []string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "String in slice (case-sensitive)",
|
|
a: "apple",
|
|
list: []string{"orange", "banana", "apple", "grape"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "String not in slice (case-sensitive)",
|
|
a: "pear",
|
|
list: []string{"orange", "banana", "apple", "grape"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "String in slice (case-insensitive)",
|
|
a: "APPLE",
|
|
list: []string{"orange", "banana", "apple", "grape"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Empty slice",
|
|
a: "apple",
|
|
list: []string{},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Empty string",
|
|
a: "",
|
|
list: []string{"orange", "banana", "apple", "grape"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Empty strings match",
|
|
a: "",
|
|
list: []string{"orange", ""},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Empty string in empty slice",
|
|
a: "",
|
|
list: []string{},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Act
|
|
got := stringInSlice(tt.a, tt.list)
|
|
|
|
// Assert
|
|
if got != tt.expected {
|
|
t.Errorf("Expected stringInSlice: %v\nGot: %v", tt.expected, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestRpad tests the rpad function with various test cases to ensure it pads strings correctly.
|
|
func TestRpad(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
inputString string
|
|
padding int
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Padding required",
|
|
inputString: "Hello",
|
|
padding: 10,
|
|
expected: "Hello ",
|
|
},
|
|
{
|
|
name: "No padding required",
|
|
inputString: "World",
|
|
padding: 5,
|
|
expected: "World",
|
|
},
|
|
{
|
|
name: "Empty string",
|
|
inputString: "",
|
|
padding: 8,
|
|
expected: " ",
|
|
},
|
|
{
|
|
name: "Zero padding",
|
|
inputString: "cobra",
|
|
padding: 0,
|
|
expected: "cobra",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Act
|
|
got := rpad(tt.inputString, tt.padding)
|
|
|
|
// Assert
|
|
if got != tt.expected {
|
|
t.Errorf("Expected rpad: %v\nGot: %v", tt.expected, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDeadcodeElimination checks that a simple program using cobra in its default configuration is linked taking full advantage of the linker's deadcode elimination step.
|
|
//
|
|
// If reflect.Value.MethodByName/reflect.Value.Method are reachable the linker will not always be able to prove that exported methods are unreachable, making deadcode elimination less effective. Using text/template and html/template makes reflect.Value.MethodByName reachable.
|
|
// Since cobra can use text/template templates this test checks that in its default configuration that code path can be proven to be unreachable by the linker.
|
|
//
|
|
// See also: https://github.com/spf13/cobra/pull/1956
|
|
func TestDeadcodeElimination(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("go tool nm fails on windows")
|
|
}
|
|
|
|
// check that a simple program using cobra in its default configuration is
|
|
// linked with deadcode elimination enabled.
|
|
const (
|
|
dirname = "test_deadcode"
|
|
progname = "test_deadcode_elimination"
|
|
)
|
|
_ = os.Mkdir(dirname, 0770)
|
|
defer os.RemoveAll(dirname)
|
|
filename := filepath.Join(dirname, progname+".go")
|
|
err := os.WriteFile(filename, []byte(`package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var rootCmd = &cobra.Command{
|
|
Version: "1.0",
|
|
Use: "example_program",
|
|
Short: "example_program - test fixture to check that deadcode elimination is allowed",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
fmt.Println("hello world")
|
|
},
|
|
Aliases: []string{"alias1", "alias2"},
|
|
Example: "stringer --help",
|
|
}
|
|
|
|
func main() {
|
|
if err := rootCmd.Execute(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Whoops. There was an error while executing your CLI '%s'", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
`), 0600)
|
|
if err != nil {
|
|
t.Fatalf("could not write test program: %v", err)
|
|
}
|
|
buf, err := exec.Command("go", "build", filename).CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("could not compile test program: %s", string(buf))
|
|
}
|
|
defer os.Remove(progname)
|
|
buf, err = exec.Command("go", "tool", "nm", progname).CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("could not run go tool nm: %v", err)
|
|
}
|
|
if strings.Contains(string(buf), "MethodByName") {
|
|
t.Error("compiled programs contains MethodByName symbol")
|
|
}
|
|
}
|