| // 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" | |
| ) | |
| func assertNoErr(t *testing.T, e error) { | |
| if e != nil { | |
| t.Error(e) | |
| } | |
| } | |
| 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) | |
| } | |
| }) | |
| } | |
| } | |
| 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) | |
| } | |
| }) | |
| } | |
| } | |
| 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") | |
| } | |
| } |