1/* 2The Ginkgo CLI 3 4The Ginkgo CLI is fully documented [here](http://onsi.github.io/ginkgo/#the_ginkgo_cli) 5 6You can also learn more by running: 7 8 ginkgo help 9 10Here are some of the more commonly used commands: 11 12To install: 13 14 go install github.com/onsi/ginkgo/ginkgo 15 16To run tests: 17 18 ginkgo 19 20To run tests in all subdirectories: 21 22 ginkgo -r 23 24To run tests in particular packages: 25 26 ginkgo <flags> /path/to/package /path/to/another/package 27 28To pass arguments/flags to your tests: 29 30 ginkgo <flags> <packages> -- <pass-throughs> 31 32To run tests in parallel 33 34 ginkgo -p 35 36this will automatically detect the optimal number of nodes to use. Alternatively, you can specify the number of nodes with: 37 38 ginkgo -nodes=N 39 40(note that you don't need to provide -p in this case). 41 42By default the Ginkgo CLI will spin up a server that the individual test processes send test output to. The CLI aggregates this output and then presents coherent test output, one test at a time, as each test completes. 43An alternative is to have the parallel nodes run and stream interleaved output back. This useful for debugging, particularly in contexts where tests hang/fail to start. To get this interleaved output: 44 45 ginkgo -nodes=N -stream=true 46 47On windows, the default value for stream is true. 48 49By default, when running multiple tests (with -r or a list of packages) Ginkgo will abort when a test fails. To have Ginkgo run subsequent test suites instead you can: 50 51 ginkgo -keepGoing 52 53To fail if there are ginkgo tests in a directory but no test suite (missing `RunSpecs`) 54 55 ginkgo -requireSuite 56 57To monitor packages and rerun tests when changes occur: 58 59 ginkgo watch <-r> </path/to/package> 60 61passing `ginkgo watch` the `-r` flag will recursively detect all test suites under the current directory and monitor them. 62`watch` does not detect *new* packages. Moreover, changes in package X only rerun the tests for package X, tests for packages 63that depend on X are not rerun. 64 65[OSX & Linux only] To receive (desktop) notifications when a test run completes: 66 67 ginkgo -notify 68 69this is particularly useful with `ginkgo watch`. Notifications are currently only supported on OS X and require that you `brew install terminal-notifier` 70 71Sometimes (to suss out race conditions/flakey tests, for example) you want to keep running a test suite until it fails. You can do this with: 72 73 ginkgo -untilItFails 74 75To bootstrap a test suite: 76 77 ginkgo bootstrap 78 79To generate a test file: 80 81 ginkgo generate <test_file_name> 82 83To bootstrap/generate test files without using "." imports: 84 85 ginkgo bootstrap --nodot 86 ginkgo generate --nodot 87 88this will explicitly export all the identifiers in Ginkgo and Gomega allowing you to rename them to avoid collisions. When you pull to the latest Ginkgo/Gomega you'll want to run 89 90 ginkgo nodot 91 92to refresh this list and pull in any new identifiers. In particular, this will pull in any new Gomega matchers that get added. 93 94To convert an existing XUnit style test suite to a Ginkgo-style test suite: 95 96 ginkgo convert . 97 98To unfocus tests: 99 100 ginkgo unfocus 101 102or 103 104 ginkgo blur 105 106To compile a test suite: 107 108 ginkgo build <path-to-package> 109 110will output an executable file named `package.test`. This can be run directly or by invoking 111 112 ginkgo <path-to-package.test> 113 114To print out Ginkgo's version: 115 116 ginkgo version 117 118To get more help: 119 120 ginkgo help 121*/ 122package main 123 124import ( 125 "flag" 126 "fmt" 127 "os" 128 "os/exec" 129 "strings" 130 131 "github.com/onsi/ginkgo/config" 132 "github.com/onsi/ginkgo/ginkgo/testsuite" 133) 134 135const greenColor = "\x1b[32m" 136const redColor = "\x1b[91m" 137const defaultStyle = "\x1b[0m" 138const lightGrayColor = "\x1b[37m" 139 140type Command struct { 141 Name string 142 AltName string 143 FlagSet *flag.FlagSet 144 Usage []string 145 UsageCommand string 146 Command func(args []string, additionalArgs []string) 147 SuppressFlagDocumentation bool 148 FlagDocSubstitute []string 149} 150 151func (c *Command) Matches(name string) bool { 152 return c.Name == name || (c.AltName != "" && c.AltName == name) 153} 154 155func (c *Command) Run(args []string, additionalArgs []string) { 156 c.FlagSet.Usage = usage 157 c.FlagSet.Parse(args) 158 c.Command(c.FlagSet.Args(), additionalArgs) 159} 160 161var DefaultCommand *Command 162var Commands []*Command 163 164func init() { 165 DefaultCommand = BuildRunCommand() 166 Commands = append(Commands, BuildWatchCommand()) 167 Commands = append(Commands, BuildBuildCommand()) 168 Commands = append(Commands, BuildBootstrapCommand()) 169 Commands = append(Commands, BuildGenerateCommand()) 170 Commands = append(Commands, BuildNodotCommand()) 171 Commands = append(Commands, BuildConvertCommand()) 172 Commands = append(Commands, BuildUnfocusCommand()) 173 Commands = append(Commands, BuildVersionCommand()) 174 Commands = append(Commands, BuildHelpCommand()) 175} 176 177func main() { 178 args := []string{} 179 additionalArgs := []string{} 180 181 foundDelimiter := false 182 183 for _, arg := range os.Args[1:] { 184 if !foundDelimiter { 185 if arg == "--" { 186 foundDelimiter = true 187 continue 188 } 189 } 190 191 if foundDelimiter { 192 additionalArgs = append(additionalArgs, arg) 193 } else { 194 args = append(args, arg) 195 } 196 } 197 198 if len(args) > 0 { 199 commandToRun, found := commandMatching(args[0]) 200 if found { 201 commandToRun.Run(args[1:], additionalArgs) 202 return 203 } 204 } 205 206 DefaultCommand.Run(args, additionalArgs) 207} 208 209func commandMatching(name string) (*Command, bool) { 210 for _, command := range Commands { 211 if command.Matches(name) { 212 return command, true 213 } 214 } 215 return nil, false 216} 217 218func usage() { 219 fmt.Printf("Ginkgo Version %s\n\n", config.VERSION) 220 usageForCommand(DefaultCommand, false) 221 for _, command := range Commands { 222 fmt.Printf("\n") 223 usageForCommand(command, false) 224 } 225} 226 227func usageForCommand(command *Command, longForm bool) { 228 fmt.Printf("%s\n%s\n", command.UsageCommand, strings.Repeat("-", len(command.UsageCommand))) 229 fmt.Printf("%s\n", strings.Join(command.Usage, "\n")) 230 if command.SuppressFlagDocumentation && !longForm { 231 fmt.Printf("%s\n", strings.Join(command.FlagDocSubstitute, "\n ")) 232 } else { 233 command.FlagSet.SetOutput(os.Stdout) 234 command.FlagSet.PrintDefaults() 235 } 236} 237 238func complainAndQuit(complaint string) { 239 fmt.Fprintf(os.Stderr, "%s\nFor usage instructions:\n\tginkgo help\n", complaint) 240 os.Exit(1) 241} 242 243func findSuites(args []string, recurseForAll bool, skipPackage string, allowPrecompiled bool) ([]testsuite.TestSuite, []string) { 244 suites := []testsuite.TestSuite{} 245 246 if len(args) > 0 { 247 for _, arg := range args { 248 if allowPrecompiled { 249 suite, err := testsuite.PrecompiledTestSuite(arg) 250 if err == nil { 251 suites = append(suites, suite) 252 continue 253 } 254 } 255 recurseForSuite := recurseForAll 256 if strings.HasSuffix(arg, "/...") && arg != "/..." { 257 arg = arg[:len(arg)-4] 258 recurseForSuite = true 259 } 260 suites = append(suites, testsuite.SuitesInDir(arg, recurseForSuite)...) 261 } 262 } else { 263 suites = testsuite.SuitesInDir(".", recurseForAll) 264 } 265 266 skippedPackages := []string{} 267 if skipPackage != "" { 268 skipFilters := strings.Split(skipPackage, ",") 269 filteredSuites := []testsuite.TestSuite{} 270 for _, suite := range suites { 271 skip := false 272 for _, skipFilter := range skipFilters { 273 if strings.Contains(suite.Path, skipFilter) { 274 skip = true 275 break 276 } 277 } 278 if skip { 279 skippedPackages = append(skippedPackages, suite.Path) 280 } else { 281 filteredSuites = append(filteredSuites, suite) 282 } 283 } 284 suites = filteredSuites 285 } 286 287 return suites, skippedPackages 288} 289 290func goFmt(path string) { 291 out, err := exec.Command("go", "fmt", path).CombinedOutput() 292 if err != nil { 293 complainAndQuit("Could not fmt: " + err.Error() + "\n" + string(out)) 294 } 295} 296 297func pluralizedWord(singular, plural string, count int) string { 298 if count == 1 { 299 return singular 300 } 301 return plural 302} 303