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