package main import ( "flag" "fmt" "regexp" "time" "github.com/onsi/ginkgo/config" "github.com/onsi/ginkgo/ginkgo/interrupthandler" "github.com/onsi/ginkgo/ginkgo/testrunner" "github.com/onsi/ginkgo/ginkgo/testsuite" "github.com/onsi/ginkgo/ginkgo/watch" colorable "github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable" ) func BuildWatchCommand() *Command { commandFlags := NewWatchCommandFlags(flag.NewFlagSet("watch", flag.ExitOnError)) interruptHandler := interrupthandler.NewInterruptHandler() notifier := NewNotifier(commandFlags) watcher := &SpecWatcher{ commandFlags: commandFlags, notifier: notifier, interruptHandler: interruptHandler, suiteRunner: NewSuiteRunner(notifier, interruptHandler), } return &Command{ Name: "watch", FlagSet: commandFlags.FlagSet, UsageCommand: "ginkgo watch -- ", Usage: []string{ "Watches the tests in the passed in and runs them when changes occur.", "Any arguments after -- will be passed to the test.", }, Command: watcher.WatchSpecs, SuppressFlagDocumentation: true, FlagDocSubstitute: []string{ "Accepts all the flags that the ginkgo command accepts except for --keepGoing and --untilItFails", }, } } type SpecWatcher struct { commandFlags *RunWatchAndBuildCommandFlags notifier *Notifier interruptHandler *interrupthandler.InterruptHandler suiteRunner *SuiteRunner } func (w *SpecWatcher) WatchSpecs(args []string, additionalArgs []string) { w.commandFlags.computeNodes() w.notifier.VerifyNotificationsAreAvailable() w.WatchSuites(args, additionalArgs) } func (w *SpecWatcher) runnersForSuites(suites []testsuite.TestSuite, additionalArgs []string) []*testrunner.TestRunner { runners := []*testrunner.TestRunner{} for _, suite := range suites { runners = append(runners, testrunner.New(suite, w.commandFlags.NumCPU, w.commandFlags.ParallelStream, w.commandFlags.Timeout, w.commandFlags.GoOpts, additionalArgs)) } return runners } func (w *SpecWatcher) WatchSuites(args []string, additionalArgs []string) { suites, _ := findSuites(args, w.commandFlags.Recurse, w.commandFlags.SkipPackage, false) if len(suites) == 0 { complainAndQuit("Found no test suites") } fmt.Printf("Identified %d test %s. Locating dependencies to a depth of %d (this may take a while)...\n", len(suites), pluralizedWord("suite", "suites", len(suites)), w.commandFlags.Depth) deltaTracker := watch.NewDeltaTracker(w.commandFlags.Depth, regexp.MustCompile(w.commandFlags.WatchRegExp)) delta, errors := deltaTracker.Delta(suites) fmt.Printf("Watching %d %s:\n", len(delta.NewSuites), pluralizedWord("suite", "suites", len(delta.NewSuites))) for _, suite := range delta.NewSuites { fmt.Println(" " + suite.Description()) } for suite, err := range errors { fmt.Printf("Failed to watch %s: %s\n", suite.PackageName, err) } if len(suites) == 1 { runners := w.runnersForSuites(suites, additionalArgs) w.suiteRunner.RunSuites(runners, w.commandFlags.NumCompilers, true, nil) runners[0].CleanUp() } ticker := time.NewTicker(time.Second) for { select { case <-ticker.C: suites, _ := findSuites(args, w.commandFlags.Recurse, w.commandFlags.SkipPackage, false) delta, _ := deltaTracker.Delta(suites) coloredStream := colorable.NewColorableStdout() suitesToRun := []testsuite.TestSuite{} if len(delta.NewSuites) > 0 { fmt.Fprintf(coloredStream, greenColor+"Detected %d new %s:\n"+defaultStyle, len(delta.NewSuites), pluralizedWord("suite", "suites", len(delta.NewSuites))) for _, suite := range delta.NewSuites { suitesToRun = append(suitesToRun, suite.Suite) fmt.Fprintln(coloredStream, " "+suite.Description()) } } modifiedSuites := delta.ModifiedSuites() if len(modifiedSuites) > 0 { fmt.Fprintln(coloredStream, greenColor+"\nDetected changes in:"+defaultStyle) for _, pkg := range delta.ModifiedPackages { fmt.Fprintln(coloredStream, " "+pkg) } fmt.Fprintf(coloredStream, greenColor+"Will run %d %s:\n"+defaultStyle, len(modifiedSuites), pluralizedWord("suite", "suites", len(modifiedSuites))) for _, suite := range modifiedSuites { suitesToRun = append(suitesToRun, suite.Suite) fmt.Fprintln(coloredStream, " "+suite.Description()) } fmt.Fprintln(coloredStream, "") } if len(suitesToRun) > 0 { w.UpdateSeed() w.ComputeSuccinctMode(len(suitesToRun)) runners := w.runnersForSuites(suitesToRun, additionalArgs) result, _ := w.suiteRunner.RunSuites(runners, w.commandFlags.NumCompilers, true, func(suite testsuite.TestSuite) { deltaTracker.WillRun(suite) }) for _, runner := range runners { runner.CleanUp() } if !w.interruptHandler.WasInterrupted() { color := redColor if result.Passed { color = greenColor } fmt.Fprintln(coloredStream, color+"\nDone. Resuming watch..."+defaultStyle) } } case <-w.interruptHandler.C: return } } } func (w *SpecWatcher) ComputeSuccinctMode(numSuites int) { if config.DefaultReporterConfig.Verbose { config.DefaultReporterConfig.Succinct = false return } if w.commandFlags.wasSet("succinct") { return } if numSuites == 1 { config.DefaultReporterConfig.Succinct = false } if numSuites > 1 { config.DefaultReporterConfig.Succinct = true } } func (w *SpecWatcher) UpdateSeed() { if !w.commandFlags.wasSet("seed") { config.GinkgoConfig.RandomSeed = time.Now().Unix() } }