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.Parse(args) 157 c.Command(c.FlagSet.Args(), additionalArgs) 158} 159 160var DefaultCommand *Command 161var Commands []*Command 162 163func init() { 164 DefaultCommand = BuildRunCommand() 165 Commands = append(Commands, BuildWatchCommand()) 166 Commands = append(Commands, BuildBuildCommand()) 167 Commands = append(Commands, BuildBootstrapCommand()) 168 Commands = append(Commands, BuildGenerateCommand()) 169 Commands = append(Commands, BuildNodotCommand()) 170 Commands = append(Commands, BuildConvertCommand()) 171 Commands = append(Commands, BuildUnfocusCommand()) 172 Commands = append(Commands, BuildVersionCommand()) 173 Commands = append(Commands, BuildHelpCommand()) 174} 175 176func main() { 177 args := []string{} 178 additionalArgs := []string{} 179 180 foundDelimiter := false 181 182 for _, arg := range os.Args[1:] { 183 if !foundDelimiter { 184 if arg == "--" { 185 foundDelimiter = true 186 continue 187 } 188 } 189 190 if foundDelimiter { 191 additionalArgs = append(additionalArgs, arg) 192 } else { 193 args = append(args, arg) 194 } 195 } 196 197 if len(args) > 0 { 198 commandToRun, found := commandMatching(args[0]) 199 if found { 200 commandToRun.Run(args[1:], additionalArgs) 201 return 202 } 203 } 204 205 DefaultCommand.Run(args, additionalArgs) 206} 207 208func commandMatching(name string) (*Command, bool) { 209 for _, command := range Commands { 210 if command.Matches(name) { 211 return command, true 212 } 213 } 214 return nil, false 215} 216 217func usage() { 218 fmt.Fprintf(os.Stderr, "Ginkgo Version %s\n\n", config.VERSION) 219 usageForCommand(DefaultCommand, false) 220 for _, command := range Commands { 221 fmt.Fprintf(os.Stderr, "\n") 222 usageForCommand(command, false) 223 } 224} 225 226func usageForCommand(command *Command, longForm bool) { 227 fmt.Fprintf(os.Stderr, "%s\n%s\n", command.UsageCommand, strings.Repeat("-", len(command.UsageCommand))) 228 fmt.Fprintf(os.Stderr, "%s\n", strings.Join(command.Usage, "\n")) 229 if command.SuppressFlagDocumentation && !longForm { 230 fmt.Fprintf(os.Stderr, "%s\n", strings.Join(command.FlagDocSubstitute, "\n ")) 231 } else { 232 command.FlagSet.PrintDefaults() 233 } 234} 235 236func complainAndQuit(complaint string) { 237 fmt.Fprintf(os.Stderr, "%s\nFor usage instructions:\n\tginkgo help\n", complaint) 238 os.Exit(1) 239} 240 241func findSuites(args []string, recurseForAll bool, skipPackage string, allowPrecompiled bool) ([]testsuite.TestSuite, []string) { 242 suites := []testsuite.TestSuite{} 243 244 if len(args) > 0 { 245 for _, arg := range args { 246 if allowPrecompiled { 247 suite, err := testsuite.PrecompiledTestSuite(arg) 248 if err == nil { 249 suites = append(suites, suite) 250 continue 251 } 252 } 253 recurseForSuite := recurseForAll 254 if strings.HasSuffix(arg, "/...") && arg != "/..." { 255 arg = arg[:len(arg)-4] 256 recurseForSuite = true 257 } 258 suites = append(suites, testsuite.SuitesInDir(arg, recurseForSuite)...) 259 } 260 } else { 261 suites = testsuite.SuitesInDir(".", recurseForAll) 262 } 263 264 skippedPackages := []string{} 265 if skipPackage != "" { 266 skipFilters := strings.Split(skipPackage, ",") 267 filteredSuites := []testsuite.TestSuite{} 268 for _, suite := range suites { 269 skip := false 270 for _, skipFilter := range skipFilters { 271 if strings.Contains(suite.Path, skipFilter) { 272 skip = true 273 break 274 } 275 } 276 if skip { 277 skippedPackages = append(skippedPackages, suite.Path) 278 } else { 279 filteredSuites = append(filteredSuites, suite) 280 } 281 } 282 suites = filteredSuites 283 } 284 285 return suites, skippedPackages 286} 287 288func goFmt(path string) { 289 err := exec.Command("go", "fmt", path).Run() 290 if err != nil { 291 complainAndQuit("Could not fmt: " + err.Error()) 292 } 293} 294 295func pluralizedWord(singular, plural string, count int) string { 296 if count == 1 { 297 return singular 298 } 299 return plural 300} 301