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