github.com/pelicanplatform/pelican@v1.0.5/cmd/main_test.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "os/exec" 8 "runtime" 9 "strings" 10 11 "testing" 12 13 "github.com/stretchr/testify/assert" 14 ) 15 16 func TestHandleCLIVersionFlag(t *testing.T) { 17 version = "0.0.1" 18 date = "2023-10-06T15:26:50Z" 19 commit = "f0f94a3edf6641c2472345819a0d5453fc9e68d1" 20 builtBy = "goreleaser" 21 22 // Reset os.Args to ensure Windows doesn't do weird things to the test 23 oldArgs := os.Args 24 os.Args = []string{os.Args[0]} 25 26 mockVersionOutput := fmt.Sprintf( 27 "Version: %s\nBuild Date: %s\nBuild Commit: %s\nBuilt By: %s", 28 version, date, commit, builtBy, 29 ) 30 31 testCases := []struct { 32 name string 33 args []string 34 expected string 35 }{ 36 // The choice of Long and Short is based on the current pattern we have 37 // that only root command has Long description and Short description 38 // for the rest of the subcommands 39 { 40 "no-flag-on-root-command", 41 []string{"pelican"}, 42 rootCmd.Long, 43 }, 44 { 45 "no-flag-on-subcommand", 46 []string{"pelican", "origin"}, 47 originCmd.Short, 48 }, 49 { 50 "flag-on-root-command", 51 []string{"pelican", "--version"}, 52 mockVersionOutput, 53 }, 54 { 55 "flag-on-subcommand", 56 []string{"pelican", "origin", "--version"}, 57 mockVersionOutput, 58 }, 59 { 60 "flag-on-second-layer-subcommand", 61 []string{"pelican", "origin", "get", "--version"}, 62 mockVersionOutput, 63 }, 64 { 65 "other-flag-on-root-command", 66 []string{"pelican", "--help"}, 67 rootCmd.Long, 68 }, 69 } 70 71 batchTest := func(t *testing.T, arguments []string, expected string) { 72 // Redirect output to a pip 73 oldStdout := os.Stdout 74 r, w, _ := os.Pipe() 75 os.Stdout = w 76 77 err := handleCLI(arguments) 78 79 // Close the write of pip and redirect output back to stdout 80 w.Close() 81 out, _ := io.ReadAll(r) 82 os.Stdout = oldStdout 83 84 got := strings.TrimSpace(string(out)) 85 assert.NoError(t, err, "Should not have error running the function") 86 if expected != mockVersionOutput { 87 // If the expected string is not the version output, use Contains to check 88 // This is mainly for checking against command help output 89 assert.Contains(t, got, expected, "Output does not match expectation") 90 } else { 91 assert.Equal(t, expected, got, "Output does not match expectation") 92 } 93 } 94 95 for _, tc := range testCases { 96 t.Run(tc.name, func(t *testing.T) { 97 batchTest(t, tc.args, tc.expected) 98 }) 99 } 100 101 // Restore the args back when test finished 102 os.Args = oldArgs 103 } 104 105 func TestHandleCLIExecutableAlias(t *testing.T) { 106 // If we're in the process started by exec.Command, run the handleCLI function. 107 if os.Getenv("BE_CRASHER") == "1" { 108 err := handleCLI(os.Args[1:]) 109 if err != nil { 110 t.Fatalf("Function returns error") 111 } 112 return 113 } 114 115 oldArgs := os.Args 116 os.Args = []string{} 117 defer func() { 118 os.Args = oldArgs 119 }() 120 testCases := []struct { 121 name string 122 args []string 123 expected string 124 }{ 125 { 126 "no-alias", 127 []string{"pelican"}, 128 rootCmd.Long, 129 }, 130 { 131 "stashcp", 132 []string{"stashcp"}, 133 "No Source or Destination", // slightly different error message, good for testing though 134 }, 135 { 136 "stash_plugin", 137 []string{"stash_plugin"}, 138 "No source or destination specified", 139 }, 140 { 141 "osdf_plugin", 142 []string{"stash_plugin"}, 143 "No source or destination specified", 144 }, 145 { 146 "pelican_xfer_plugin", 147 []string{"stash_plugin"}, 148 "No source or destination specified", 149 }, 150 } 151 152 batchTest := func(t *testing.T, arguments []string, expected string) { 153 // Compile the test binary. 154 cmd := exec.Command("go", "build", "-o", arguments[0], ".") 155 err := cmd.Run() 156 if err != nil { 157 t.Fatal(err) 158 } 159 defer os.Remove(arguments[0]) // Clean up the test binary when done. 160 161 // Run the test binary with the BE_CRASHER environment variable set. 162 cmd = exec.Command("./"+arguments[0], arguments[1:]...) 163 cmd.Env = append(os.Environ(), "BE_CRASHER=1") 164 165 // Set up pipes to capture stdout and stderr. 166 stdout, _ := cmd.StdoutPipe() 167 stderr, _ := cmd.StderrPipe() 168 if err := cmd.Start(); err != nil { 169 t.Fatal(err) 170 } 171 172 // Read and capture stdout and stderr. 173 gotBytes, _ := io.ReadAll(stdout) 174 errBytes, _ := io.ReadAll(stderr) 175 176 // Wait for the command to finish. 177 err = cmd.Wait() 178 179 got := strings.TrimSpace(string(gotBytes)) 180 errString := strings.TrimSpace(string(errBytes)) 181 182 // Now you can check the output and the error against your expectations. 183 // If the command exited with a non-zero status, 'err' will be non-nil. 184 if err != nil { 185 _, ok := err.(*exec.ExitError) 186 if !ok { 187 t.Fatal("Failed to cast error as *exec.ExitError") 188 } 189 } 190 // Apparently both stashcp and *_plug will trigger Exit(1) with error if 191 // the arguments are not enough/solid 192 if strings.ToLower(strings.TrimSuffix(arguments[0], ".exe")) != "pelican" { 193 assert.Contains(t, errString, expected, "Output does not match expectation") 194 } else { 195 assert.NoError(t, err, "Should not have error running the function: "+errString) 196 assert.Contains(t, got, expected, "Output does not match expectation") 197 } 198 } 199 for _, tc := range testCases { 200 if os := runtime.GOOS; os == "windows" { 201 // On Windows, you can only do *.exe 202 t.Run(tc.name+"-windows", func(t *testing.T) { 203 preserve := tc.args[0] 204 tc.args[0] = preserve + ".exe" 205 batchTest(t, tc.args, tc.expected) 206 tc.args[0] = preserve 207 }) 208 } else { 209 t.Run(tc.name, func(t *testing.T) { 210 batchTest(t, tc.args, tc.expected) 211 }) 212 t.Run(tc.name+"-windows", func(t *testing.T) { 213 preserve := tc.args[0] 214 tc.args[0] = preserve + ".exe" 215 batchTest(t, tc.args, tc.expected) 216 tc.args[0] = preserve 217 }) 218 t.Run(tc.name+"-mixedCase", func(t *testing.T) { 219 preserve := tc.args[0] 220 tc.args[0] = strings.ToUpper(preserve) 221 batchTest(t, tc.args, tc.expected) 222 tc.args[0] = preserve 223 }) 224 } 225 } 226 }