From 175fc3867a70dd5a0d5c0becdb828751ccd88e9c Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Wed, 20 Jan 2021 10:31:18 +0100 Subject: Revert "ginkgo: install on demand via `go get -u`" This reverts commit de05e5816869073600ae8e851093b4b9a9d7fab0. Running `go get -u` will change the local Go module causing CI to fail as the local git tree is being changed. Reverting the change for now until we have a better idea. Signed-off-by: Valentin Rothberg --- .../onsi/ginkgo/ginkgo/bootstrap_command.go | 199 ++++++++ .../github.com/onsi/ginkgo/ginkgo/build_command.go | 66 +++ .../onsi/ginkgo/ginkgo/convert/ginkgo_ast_nodes.go | 123 +++++ .../onsi/ginkgo/ginkgo/convert/import.go | 90 ++++ .../onsi/ginkgo/ginkgo/convert/package_rewriter.go | 128 +++++ .../onsi/ginkgo/ginkgo/convert/test_finder.go | 56 +++ .../ginkgo/ginkgo/convert/testfile_rewriter.go | 162 ++++++ .../ginkgo/ginkgo/convert/testing_t_rewriter.go | 130 +++++ .../onsi/ginkgo/ginkgo/convert_command.go | 45 ++ .../onsi/ginkgo/ginkgo/generate_command.go | 256 ++++++++++ .../github.com/onsi/ginkgo/ginkgo/help_command.go | 31 ++ .../ginkgo/interrupthandler/interrupt_handler.go | 52 ++ .../interrupthandler/sigquit_swallower_unix.go | 14 + .../interrupthandler/sigquit_swallower_windows.go | 7 + vendor/github.com/onsi/ginkgo/ginkgo/main.go | 302 +++++++++++ .../github.com/onsi/ginkgo/ginkgo/nodot/nodot.go | 196 ++++++++ .../github.com/onsi/ginkgo/ginkgo/nodot_command.go | 77 +++ .../github.com/onsi/ginkgo/ginkgo/notifications.go | 141 ++++++ .../github.com/onsi/ginkgo/ginkgo/run_command.go | 291 +++++++++++ .../ginkgo/run_watch_and_build_command_flags.go | 169 +++++++ .../github.com/onsi/ginkgo/ginkgo/suite_runner.go | 173 +++++++ .../onsi/ginkgo/ginkgo/testrunner/build_args.go | 7 + .../ginkgo/ginkgo/testrunner/build_args_old.go | 7 + .../onsi/ginkgo/ginkgo/testrunner/log_writer.go | 52 ++ .../onsi/ginkgo/ginkgo/testrunner/run_result.go | 27 + .../onsi/ginkgo/ginkgo/testrunner/test_runner.go | 554 +++++++++++++++++++++ .../onsi/ginkgo/ginkgo/testsuite/test_suite.go | 115 +++++ .../ginkgo/ginkgo/testsuite/vendor_check_go15.go | 16 + .../ginkgo/ginkgo/testsuite/vendor_check_go16.go | 15 + .../onsi/ginkgo/ginkgo/unfocus_command.go | 180 +++++++ .../onsi/ginkgo/ginkgo/version_command.go | 24 + .../github.com/onsi/ginkgo/ginkgo/watch/delta.go | 22 + .../onsi/ginkgo/ginkgo/watch/delta_tracker.go | 75 +++ .../onsi/ginkgo/ginkgo/watch/dependencies.go | 92 ++++ .../onsi/ginkgo/ginkgo/watch/package_hash.go | 104 ++++ .../onsi/ginkgo/ginkgo/watch/package_hashes.go | 85 ++++ .../github.com/onsi/ginkgo/ginkgo/watch/suite.go | 87 ++++ .../github.com/onsi/ginkgo/ginkgo/watch_command.go | 175 +++++++ 38 files changed, 4345 insertions(+) create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/bootstrap_command.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/build_command.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/convert/ginkgo_ast_nodes.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/convert/import.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/convert/package_rewriter.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/convert/test_finder.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/convert/testfile_rewriter.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/convert/testing_t_rewriter.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/convert_command.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/generate_command.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/help_command.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/interrupthandler/interrupt_handler.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/interrupthandler/sigquit_swallower_unix.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/interrupthandler/sigquit_swallower_windows.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/main.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/nodot/nodot.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/nodot_command.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/notifications.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/run_command.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/run_watch_and_build_command_flags.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/suite_runner.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/testrunner/build_args.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/testrunner/build_args_old.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/testrunner/log_writer.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/testrunner/run_result.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/testrunner/test_runner.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/testsuite/test_suite.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/testsuite/vendor_check_go15.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/testsuite/vendor_check_go16.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/unfocus_command.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/version_command.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/watch/delta.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/watch/delta_tracker.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/watch/dependencies.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/watch/package_hash.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/watch/package_hashes.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/watch/suite.go create mode 100644 vendor/github.com/onsi/ginkgo/ginkgo/watch_command.go (limited to 'vendor/github.com/onsi') diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/bootstrap_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/bootstrap_command.go new file mode 100644 index 000000000..93150d1a4 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/bootstrap_command.go @@ -0,0 +1,199 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "text/template" + + "go/build" + + "github.com/onsi/ginkgo/ginkgo/nodot" +) + +func BuildBootstrapCommand() *Command { + var ( + agouti, noDot, internal bool + customBootstrapFile string + ) + flagSet := flag.NewFlagSet("bootstrap", flag.ExitOnError) + flagSet.BoolVar(&agouti, "agouti", false, "If set, bootstrap will generate a bootstrap file for writing Agouti tests") + flagSet.BoolVar(&noDot, "nodot", false, "If set, bootstrap will generate a bootstrap file that does not . import ginkgo and gomega") + flagSet.BoolVar(&internal, "internal", false, "If set, generate will generate a test file that uses the regular package name") + flagSet.StringVar(&customBootstrapFile, "template", "", "If specified, generate will use the contents of the file passed as the bootstrap template") + + return &Command{ + Name: "bootstrap", + FlagSet: flagSet, + UsageCommand: "ginkgo bootstrap ", + Usage: []string{ + "Bootstrap a test suite for the current package", + "Accepts the following flags:", + }, + Command: func(args []string, additionalArgs []string) { + generateBootstrap(agouti, noDot, internal, customBootstrapFile) + }, + } +} + +var bootstrapText = `package {{.Package}} + +import ( + "testing" + + {{.GinkgoImport}} + {{.GomegaImport}} +) + +func Test{{.FormattedName}}(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "{{.FormattedName}} Suite") +} +` + +var agoutiBootstrapText = `package {{.Package}} + +import ( + "testing" + + {{.GinkgoImport}} + {{.GomegaImport}} + "github.com/sclevine/agouti" +) + +func Test{{.FormattedName}}(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "{{.FormattedName}} Suite") +} + +var agoutiDriver *agouti.WebDriver + +var _ = BeforeSuite(func() { + // Choose a WebDriver: + + agoutiDriver = agouti.PhantomJS() + // agoutiDriver = agouti.Selenium() + // agoutiDriver = agouti.ChromeDriver() + + Expect(agoutiDriver.Start()).To(Succeed()) +}) + +var _ = AfterSuite(func() { + Expect(agoutiDriver.Stop()).To(Succeed()) +}) +` + +type bootstrapData struct { + Package string + FormattedName string + GinkgoImport string + GomegaImport string +} + +func getPackageAndFormattedName() (string, string, string) { + path, err := os.Getwd() + if err != nil { + complainAndQuit("Could not get current working directory: \n" + err.Error()) + } + + dirName := strings.Replace(filepath.Base(path), "-", "_", -1) + dirName = strings.Replace(dirName, " ", "_", -1) + + pkg, err := build.ImportDir(path, 0) + packageName := pkg.Name + if err != nil { + packageName = dirName + } + + formattedName := prettifyPackageName(filepath.Base(path)) + return packageName, dirName, formattedName +} + +func prettifyPackageName(name string) string { + name = strings.Replace(name, "-", " ", -1) + name = strings.Replace(name, "_", " ", -1) + name = strings.Title(name) + name = strings.Replace(name, " ", "", -1) + return name +} + +func determinePackageName(name string, internal bool) string { + if internal { + return name + } + + return name + "_test" +} + +func fileExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +func generateBootstrap(agouti, noDot, internal bool, customBootstrapFile string) { + packageName, bootstrapFilePrefix, formattedName := getPackageAndFormattedName() + data := bootstrapData{ + Package: determinePackageName(packageName, internal), + FormattedName: formattedName, + GinkgoImport: `. "github.com/onsi/ginkgo"`, + GomegaImport: `. "github.com/onsi/gomega"`, + } + + if noDot { + data.GinkgoImport = `"github.com/onsi/ginkgo"` + data.GomegaImport = `"github.com/onsi/gomega"` + } + + targetFile := fmt.Sprintf("%s_suite_test.go", bootstrapFilePrefix) + if fileExists(targetFile) { + fmt.Printf("%s already exists.\n\n", targetFile) + os.Exit(1) + } else { + fmt.Printf("Generating ginkgo test suite bootstrap for %s in:\n\t%s\n", packageName, targetFile) + } + + f, err := os.Create(targetFile) + if err != nil { + complainAndQuit("Could not create file: " + err.Error()) + panic(err.Error()) + } + defer f.Close() + + var templateText string + if customBootstrapFile != "" { + tpl, err := ioutil.ReadFile(customBootstrapFile) + if err != nil { + panic(err.Error()) + } + templateText = string(tpl) + } else if agouti { + templateText = agoutiBootstrapText + } else { + templateText = bootstrapText + } + + bootstrapTemplate, err := template.New("bootstrap").Parse(templateText) + if err != nil { + panic(err.Error()) + } + + buf := &bytes.Buffer{} + bootstrapTemplate.Execute(buf, data) + + if noDot { + contents, err := nodot.ApplyNoDot(buf.Bytes()) + if err != nil { + complainAndQuit("Failed to import nodot declarations: " + err.Error()) + } + fmt.Println("To update the nodot declarations in the future, switch to this directory and run:\n\tginkgo nodot") + buf = bytes.NewBuffer(contents) + } + + buf.WriteTo(f) + + goFmt(targetFile) +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/build_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/build_command.go new file mode 100644 index 000000000..2fddef0f7 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/build_command.go @@ -0,0 +1,66 @@ +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" + + "github.com/onsi/ginkgo/ginkgo/interrupthandler" + "github.com/onsi/ginkgo/ginkgo/testrunner" +) + +func BuildBuildCommand() *Command { + commandFlags := NewBuildCommandFlags(flag.NewFlagSet("build", flag.ExitOnError)) + interruptHandler := interrupthandler.NewInterruptHandler() + builder := &SpecBuilder{ + commandFlags: commandFlags, + interruptHandler: interruptHandler, + } + + return &Command{ + Name: "build", + FlagSet: commandFlags.FlagSet, + UsageCommand: "ginkgo build ", + Usage: []string{ + "Build the passed in (or the package in the current directory if left blank).", + "Accepts the following flags:", + }, + Command: builder.BuildSpecs, + } +} + +type SpecBuilder struct { + commandFlags *RunWatchAndBuildCommandFlags + interruptHandler *interrupthandler.InterruptHandler +} + +func (r *SpecBuilder) BuildSpecs(args []string, additionalArgs []string) { + r.commandFlags.computeNodes() + + suites, _ := findSuites(args, r.commandFlags.Recurse, r.commandFlags.SkipPackage, false) + + if len(suites) == 0 { + complainAndQuit("Found no test suites") + } + + passed := true + for _, suite := range suites { + runner := testrunner.New(suite, 1, false, 0, r.commandFlags.GoOpts, nil) + fmt.Printf("Compiling %s...\n", suite.PackageName) + + path, _ := filepath.Abs(filepath.Join(suite.Path, fmt.Sprintf("%s.test", suite.PackageName))) + err := runner.CompileTo(path) + if err != nil { + fmt.Println(err.Error()) + passed = false + } else { + fmt.Printf(" compiled %s.test\n", suite.PackageName) + } + } + + if passed { + os.Exit(0) + } + os.Exit(1) +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/convert/ginkgo_ast_nodes.go b/vendor/github.com/onsi/ginkgo/ginkgo/convert/ginkgo_ast_nodes.go new file mode 100644 index 000000000..02e2b3b32 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/convert/ginkgo_ast_nodes.go @@ -0,0 +1,123 @@ +package convert + +import ( + "fmt" + "go/ast" + "strings" + "unicode" +) + +/* + * Creates a func init() node + */ +func createVarUnderscoreBlock() *ast.ValueSpec { + valueSpec := &ast.ValueSpec{} + object := &ast.Object{Kind: 4, Name: "_", Decl: valueSpec, Data: 0} + ident := &ast.Ident{Name: "_", Obj: object} + valueSpec.Names = append(valueSpec.Names, ident) + return valueSpec +} + +/* + * Creates a Describe("Testing with ginkgo", func() { }) node + */ +func createDescribeBlock() *ast.CallExpr { + blockStatement := &ast.BlockStmt{List: []ast.Stmt{}} + + fieldList := &ast.FieldList{} + funcType := &ast.FuncType{Params: fieldList} + funcLit := &ast.FuncLit{Type: funcType, Body: blockStatement} + basicLit := &ast.BasicLit{Kind: 9, Value: "\"Testing with Ginkgo\""} + describeIdent := &ast.Ident{Name: "Describe"} + return &ast.CallExpr{Fun: describeIdent, Args: []ast.Expr{basicLit, funcLit}} +} + +/* + * Convenience function to return the name of the *testing.T param + * for a Test function that will be rewritten. This is useful because + * we will want to replace the usage of this named *testing.T inside the + * body of the function with a GinktoT. + */ +func namedTestingTArg(node *ast.FuncDecl) string { + return node.Type.Params.List[0].Names[0].Name // *exhale* +} + +/* + * Convenience function to return the block statement node for a Describe statement + */ +func blockStatementFromDescribe(desc *ast.CallExpr) *ast.BlockStmt { + var funcLit *ast.FuncLit + var found = false + + for _, node := range desc.Args { + switch node := node.(type) { + case *ast.FuncLit: + found = true + funcLit = node + break + } + } + + if !found { + panic("Error finding ast.FuncLit inside describe statement. Somebody done goofed.") + } + + return funcLit.Body +} + +/* convenience function for creating an It("TestNameHere") + * with all the body of the test function inside the anonymous + * func passed to It() + */ +func createItStatementForTestFunc(testFunc *ast.FuncDecl) *ast.ExprStmt { + blockStatement := &ast.BlockStmt{List: testFunc.Body.List} + fieldList := &ast.FieldList{} + funcType := &ast.FuncType{Params: fieldList} + funcLit := &ast.FuncLit{Type: funcType, Body: blockStatement} + + testName := rewriteTestName(testFunc.Name.Name) + basicLit := &ast.BasicLit{Kind: 9, Value: fmt.Sprintf("\"%s\"", testName)} + itBlockIdent := &ast.Ident{Name: "It"} + callExpr := &ast.CallExpr{Fun: itBlockIdent, Args: []ast.Expr{basicLit, funcLit}} + return &ast.ExprStmt{X: callExpr} +} + +/* +* rewrite test names to be human readable +* eg: rewrites "TestSomethingAmazing" as "something amazing" + */ +func rewriteTestName(testName string) string { + nameComponents := []string{} + currentString := "" + indexOfTest := strings.Index(testName, "Test") + if indexOfTest != 0 { + return testName + } + + testName = strings.Replace(testName, "Test", "", 1) + first, rest := testName[0], testName[1:] + testName = string(unicode.ToLower(rune(first))) + rest + + for _, rune := range testName { + if unicode.IsUpper(rune) { + nameComponents = append(nameComponents, currentString) + currentString = string(unicode.ToLower(rune)) + } else { + currentString += string(rune) + } + } + + return strings.Join(append(nameComponents, currentString), " ") +} + +func newGinkgoTFromIdent(ident *ast.Ident) *ast.CallExpr { + return &ast.CallExpr{ + Lparen: ident.NamePos + 1, + Rparen: ident.NamePos + 2, + Fun: &ast.Ident{Name: "GinkgoT"}, + } +} + +func newGinkgoTInterface() *ast.Ident { + return &ast.Ident{Name: "GinkgoTInterface"} +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/convert/import.go b/vendor/github.com/onsi/ginkgo/ginkgo/convert/import.go new file mode 100644 index 000000000..06c6ec94c --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/convert/import.go @@ -0,0 +1,90 @@ +package convert + +import ( + "fmt" + "go/ast" +) + +/* + * Given the root node of an AST, returns the node containing the + * import statements for the file. + */ +func importsForRootNode(rootNode *ast.File) (imports *ast.GenDecl, err error) { + for _, declaration := range rootNode.Decls { + decl, ok := declaration.(*ast.GenDecl) + if !ok || len(decl.Specs) == 0 { + continue + } + + _, ok = decl.Specs[0].(*ast.ImportSpec) + if ok { + imports = decl + return + } + } + + err = fmt.Errorf("Could not find imports for root node:\n\t%#v\n", rootNode) + return +} + +/* + * Removes "testing" import, if present + */ +func removeTestingImport(rootNode *ast.File) { + importDecl, err := importsForRootNode(rootNode) + if err != nil { + panic(err.Error()) + } + + var index int + for i, importSpec := range importDecl.Specs { + importSpec := importSpec.(*ast.ImportSpec) + if importSpec.Path.Value == "\"testing\"" { + index = i + break + } + } + + importDecl.Specs = append(importDecl.Specs[:index], importDecl.Specs[index+1:]...) +} + +/* + * Adds import statements for onsi/ginkgo, if missing + */ +func addGinkgoImports(rootNode *ast.File) { + importDecl, err := importsForRootNode(rootNode) + if err != nil { + panic(err.Error()) + } + + if len(importDecl.Specs) == 0 { + // TODO: might need to create a import decl here + panic("unimplemented : expected to find an imports block") + } + + needsGinkgo := true + for _, importSpec := range importDecl.Specs { + importSpec, ok := importSpec.(*ast.ImportSpec) + if !ok { + continue + } + + if importSpec.Path.Value == "\"github.com/onsi/ginkgo\"" { + needsGinkgo = false + } + } + + if needsGinkgo { + importDecl.Specs = append(importDecl.Specs, createImport(".", "\"github.com/onsi/ginkgo\"")) + } +} + +/* + * convenience function to create an import statement + */ +func createImport(name, path string) *ast.ImportSpec { + return &ast.ImportSpec{ + Name: &ast.Ident{Name: name}, + Path: &ast.BasicLit{Kind: 9, Value: path}, + } +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/convert/package_rewriter.go b/vendor/github.com/onsi/ginkgo/ginkgo/convert/package_rewriter.go new file mode 100644 index 000000000..363e52fe2 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/convert/package_rewriter.go @@ -0,0 +1,128 @@ +package convert + +import ( + "fmt" + "go/build" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "regexp" +) + +/* + * RewritePackage takes a name (eg: my-package/tools), finds its test files using + * Go's build package, and then rewrites them. A ginkgo test suite file will + * also be added for this package, and all of its child packages. + */ +func RewritePackage(packageName string) { + pkg, err := packageWithName(packageName) + if err != nil { + panic(fmt.Sprintf("unexpected error reading package: '%s'\n%s\n", packageName, err.Error())) + } + + for _, filename := range findTestsInPackage(pkg) { + rewriteTestsInFile(filename) + } +} + +/* + * Given a package, findTestsInPackage reads the test files in the directory, + * and then recurses on each child package, returning a slice of all test files + * found in this process. + */ +func findTestsInPackage(pkg *build.Package) (testfiles []string) { + for _, file := range append(pkg.TestGoFiles, pkg.XTestGoFiles...) { + testfile, _ := filepath.Abs(filepath.Join(pkg.Dir, file)) + testfiles = append(testfiles, testfile) + } + + dirFiles, err := ioutil.ReadDir(pkg.Dir) + if err != nil { + panic(fmt.Sprintf("unexpected error reading dir: '%s'\n%s\n", pkg.Dir, err.Error())) + } + + re := regexp.MustCompile(`^[._]`) + + for _, file := range dirFiles { + if !file.IsDir() { + continue + } + + if re.Match([]byte(file.Name())) { + continue + } + + packageName := filepath.Join(pkg.ImportPath, file.Name()) + subPackage, err := packageWithName(packageName) + if err != nil { + panic(fmt.Sprintf("unexpected error reading package: '%s'\n%s\n", packageName, err.Error())) + } + + testfiles = append(testfiles, findTestsInPackage(subPackage)...) + } + + addGinkgoSuiteForPackage(pkg) + goFmtPackage(pkg) + return +} + +/* + * Shells out to `ginkgo bootstrap` to create a test suite file + */ +func addGinkgoSuiteForPackage(pkg *build.Package) { + originalDir, err := os.Getwd() + if err != nil { + panic(err) + } + + suite_test_file := filepath.Join(pkg.Dir, pkg.Name+"_suite_test.go") + + _, err = os.Stat(suite_test_file) + if err == nil { + return // test file already exists, this should be a no-op + } + + err = os.Chdir(pkg.Dir) + if err != nil { + panic(err) + } + + output, err := exec.Command("ginkgo", "bootstrap").Output() + + if err != nil { + panic(fmt.Sprintf("error running 'ginkgo bootstrap'.\nstdout: %s\n%s\n", output, err.Error())) + } + + err = os.Chdir(originalDir) + if err != nil { + panic(err) + } +} + +/* + * Shells out to `go fmt` to format the package + */ +func goFmtPackage(pkg *build.Package) { + path, _ := filepath.Abs(pkg.ImportPath) + output, err := exec.Command("go", "fmt", path).CombinedOutput() + + if err != nil { + fmt.Printf("Warning: Error running 'go fmt %s'.\nstdout: %s\n%s\n", path, output, err.Error()) + } +} + +/* + * Attempts to return a package with its test files already read. + * The ImportMode arg to build.Import lets you specify if you want go to read the + * buildable go files inside the package, but it fails if the package has no go files + */ +func packageWithName(name string) (pkg *build.Package, err error) { + pkg, err = build.Default.Import(name, ".", build.ImportMode(0)) + if err == nil { + return + } + + pkg, err = build.Default.Import(name, ".", build.ImportMode(1)) + return +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/convert/test_finder.go b/vendor/github.com/onsi/ginkgo/ginkgo/convert/test_finder.go new file mode 100644 index 000000000..b33595c9a --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/convert/test_finder.go @@ -0,0 +1,56 @@ +package convert + +import ( + "go/ast" + "regexp" +) + +/* + * Given a root node, walks its top level statements and returns + * points to function nodes to rewrite as It statements. + * These functions, according to Go testing convention, must be named + * TestWithCamelCasedName and receive a single *testing.T argument. + */ +func findTestFuncs(rootNode *ast.File) (testsToRewrite []*ast.FuncDecl) { + testNameRegexp := regexp.MustCompile("^Test[0-9A-Z].+") + + ast.Inspect(rootNode, func(node ast.Node) bool { + if node == nil { + return false + } + + switch node := node.(type) { + case *ast.FuncDecl: + matches := testNameRegexp.MatchString(node.Name.Name) + + if matches && receivesTestingT(node) { + testsToRewrite = append(testsToRewrite, node) + } + } + + return true + }) + + return +} + +/* + * convenience function that looks at args to a function and determines if its + * params include an argument of type *testing.T + */ +func receivesTestingT(node *ast.FuncDecl) bool { + if len(node.Type.Params.List) != 1 { + return false + } + + base, ok := node.Type.Params.List[0].Type.(*ast.StarExpr) + if !ok { + return false + } + + intermediate := base.X.(*ast.SelectorExpr) + isTestingPackage := intermediate.X.(*ast.Ident).Name == "testing" + isTestingT := intermediate.Sel.Name == "T" + + return isTestingPackage && isTestingT +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/convert/testfile_rewriter.go b/vendor/github.com/onsi/ginkgo/ginkgo/convert/testfile_rewriter.go new file mode 100644 index 000000000..60c73504a --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/convert/testfile_rewriter.go @@ -0,0 +1,162 @@ +package convert + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "io/ioutil" + "os" +) + +/* + * Given a file path, rewrites any tests in the Ginkgo format. + * First, we parse the AST, and update the imports declaration. + * Then, we walk the first child elements in the file, returning tests to rewrite. + * A top level init func is declared, with a single Describe func inside. + * Then the test functions to rewrite are inserted as It statements inside the Describe. + * Finally we walk the rest of the file, replacing other usages of *testing.T + * Once that is complete, we write the AST back out again to its file. + */ +func rewriteTestsInFile(pathToFile string) { + fileSet := token.NewFileSet() + rootNode, err := parser.ParseFile(fileSet, pathToFile, nil, parser.ParseComments) + if err != nil { + panic(fmt.Sprintf("Error parsing test file '%s':\n%s\n", pathToFile, err.Error())) + } + + addGinkgoImports(rootNode) + removeTestingImport(rootNode) + + varUnderscoreBlock := createVarUnderscoreBlock() + describeBlock := createDescribeBlock() + varUnderscoreBlock.Values = []ast.Expr{describeBlock} + + for _, testFunc := range findTestFuncs(rootNode) { + rewriteTestFuncAsItStatement(testFunc, rootNode, describeBlock) + } + + underscoreDecl := &ast.GenDecl{ + Tok: 85, // gah, magick numbers are needed to make this work + TokPos: 14, // this tricks Go into writing "var _ = Describe" + Specs: []ast.Spec{varUnderscoreBlock}, + } + + imports := rootNode.Decls[0] + tail := rootNode.Decls[1:] + rootNode.Decls = append(append([]ast.Decl{imports}, underscoreDecl), tail...) + rewriteOtherFuncsToUseGinkgoT(rootNode.Decls) + walkNodesInRootNodeReplacingTestingT(rootNode) + + var buffer bytes.Buffer + if err = format.Node(&buffer, fileSet, rootNode); err != nil { + panic(fmt.Sprintf("Error formatting ast node after rewriting tests.\n%s\n", err.Error())) + } + + fileInfo, err := os.Stat(pathToFile) + + if err != nil { + panic(fmt.Sprintf("Error stat'ing file: %s\n", pathToFile)) + } + + err = ioutil.WriteFile(pathToFile, buffer.Bytes(), fileInfo.Mode()) +} + +/* + * Given a test func named TestDoesSomethingNeat, rewrites it as + * It("does something neat", func() { __test_body_here__ }) and adds it + * to the Describe's list of statements + */ +func rewriteTestFuncAsItStatement(testFunc *ast.FuncDecl, rootNode *ast.File, describe *ast.CallExpr) { + var funcIndex int = -1 + for index, child := range rootNode.Decls { + if child == testFunc { + funcIndex = index + break + } + } + + if funcIndex < 0 { + panic(fmt.Sprintf("Assert failed: Error finding index for test node %s\n", testFunc.Name.Name)) + } + + var block *ast.BlockStmt = blockStatementFromDescribe(describe) + block.List = append(block.List, createItStatementForTestFunc(testFunc)) + replaceTestingTsWithGinkgoT(block, namedTestingTArg(testFunc)) + + // remove the old test func from the root node's declarations + rootNode.Decls = append(rootNode.Decls[:funcIndex], rootNode.Decls[funcIndex+1:]...) +} + +/* + * walks nodes inside of a test func's statements and replaces the usage of + * it's named *testing.T param with GinkgoT's + */ +func replaceTestingTsWithGinkgoT(statementsBlock *ast.BlockStmt, testingT string) { + ast.Inspect(statementsBlock, func(node ast.Node) bool { + if node == nil { + return false + } + + keyValueExpr, ok := node.(*ast.KeyValueExpr) + if ok { + replaceNamedTestingTsInKeyValueExpression(keyValueExpr, testingT) + return true + } + + funcLiteral, ok := node.(*ast.FuncLit) + if ok { + replaceTypeDeclTestingTsInFuncLiteral(funcLiteral) + return true + } + + callExpr, ok := node.(*ast.CallExpr) + if !ok { + return true + } + replaceTestingTsInArgsLists(callExpr, testingT) + + funCall, ok := callExpr.Fun.(*ast.SelectorExpr) + if ok { + replaceTestingTsMethodCalls(funCall, testingT) + } + + return true + }) +} + +/* + * rewrite t.Fail() or any other *testing.T method by replacing with T().Fail() + * This function receives a selector expression (eg: t.Fail()) and + * the name of the *testing.T param from the function declaration. Rewrites the + * selector expression in place if the target was a *testing.T + */ +func replaceTestingTsMethodCalls(selectorExpr *ast.SelectorExpr, testingT string) { + ident, ok := selectorExpr.X.(*ast.Ident) + if !ok { + return + } + + if ident.Name == testingT { + selectorExpr.X = newGinkgoTFromIdent(ident) + } +} + +/* + * replaces usages of a named *testing.T param inside of a call expression + * with a new GinkgoT object + */ +func replaceTestingTsInArgsLists(callExpr *ast.CallExpr, testingT string) { + for index, arg := range callExpr.Args { + ident, ok := arg.(*ast.Ident) + if !ok { + continue + } + + if ident.Name == testingT { + callExpr.Args[index] = newGinkgoTFromIdent(ident) + } + } +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/convert/testing_t_rewriter.go b/vendor/github.com/onsi/ginkgo/ginkgo/convert/testing_t_rewriter.go new file mode 100644 index 000000000..418cdc4e5 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/convert/testing_t_rewriter.go @@ -0,0 +1,130 @@ +package convert + +import ( + "go/ast" +) + +/* + * Rewrites any other top level funcs that receive a *testing.T param + */ +func rewriteOtherFuncsToUseGinkgoT(declarations []ast.Decl) { + for _, decl := range declarations { + decl, ok := decl.(*ast.FuncDecl) + if !ok { + continue + } + + for _, param := range decl.Type.Params.List { + starExpr, ok := param.Type.(*ast.StarExpr) + if !ok { + continue + } + + selectorExpr, ok := starExpr.X.(*ast.SelectorExpr) + if !ok { + continue + } + + xIdent, ok := selectorExpr.X.(*ast.Ident) + if !ok || xIdent.Name != "testing" { + continue + } + + if selectorExpr.Sel.Name != "T" { + continue + } + + param.Type = newGinkgoTInterface() + } + } +} + +/* + * Walks all of the nodes in the file, replacing *testing.T in struct + * and func literal nodes. eg: + * type foo struct { *testing.T } + * var bar = func(t *testing.T) { } + */ +func walkNodesInRootNodeReplacingTestingT(rootNode *ast.File) { + ast.Inspect(rootNode, func(node ast.Node) bool { + if node == nil { + return false + } + + switch node := node.(type) { + case *ast.StructType: + replaceTestingTsInStructType(node) + case *ast.FuncLit: + replaceTypeDeclTestingTsInFuncLiteral(node) + } + + return true + }) +} + +/* + * replaces named *testing.T inside a composite literal + */ +func replaceNamedTestingTsInKeyValueExpression(kve *ast.KeyValueExpr, testingT string) { + ident, ok := kve.Value.(*ast.Ident) + if !ok { + return + } + + if ident.Name == testingT { + kve.Value = newGinkgoTFromIdent(ident) + } +} + +/* + * replaces *testing.T params in a func literal with GinkgoT + */ +func replaceTypeDeclTestingTsInFuncLiteral(functionLiteral *ast.FuncLit) { + for _, arg := range functionLiteral.Type.Params.List { + starExpr, ok := arg.Type.(*ast.StarExpr) + if !ok { + continue + } + + selectorExpr, ok := starExpr.X.(*ast.SelectorExpr) + if !ok { + continue + } + + target, ok := selectorExpr.X.(*ast.Ident) + if !ok { + continue + } + + if target.Name == "testing" && selectorExpr.Sel.Name == "T" { + arg.Type = newGinkgoTInterface() + } + } +} + +/* + * Replaces *testing.T types inside of a struct declaration with a GinkgoT + * eg: type foo struct { *testing.T } + */ +func replaceTestingTsInStructType(structType *ast.StructType) { + for _, field := range structType.Fields.List { + starExpr, ok := field.Type.(*ast.StarExpr) + if !ok { + continue + } + + selectorExpr, ok := starExpr.X.(*ast.SelectorExpr) + if !ok { + continue + } + + xIdent, ok := selectorExpr.X.(*ast.Ident) + if !ok { + continue + } + + if xIdent.Name == "testing" && selectorExpr.Sel.Name == "T" { + field.Type = newGinkgoTInterface() + } + } +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/convert_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/convert_command.go new file mode 100644 index 000000000..5944ed85c --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/convert_command.go @@ -0,0 +1,45 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/onsi/ginkgo/ginkgo/convert" +) + +func BuildConvertCommand() *Command { + return &Command{ + Name: "convert", + FlagSet: flag.NewFlagSet("convert", flag.ExitOnError), + UsageCommand: "ginkgo convert /path/to/package", + Usage: []string{ + "Convert the package at the passed in path from an XUnit-style test to a Ginkgo-style test", + }, + Command: convertPackage, + } +} + +func convertPackage(args []string, additionalArgs []string) { + if len(args) != 1 { + println(fmt.Sprintf("usage: ginkgo convert /path/to/your/package")) + os.Exit(1) + } + + defer func() { + err := recover() + if err != nil { + switch err := err.(type) { + case error: + println(err.Error()) + case string: + println(err) + default: + println(fmt.Sprintf("unexpected error: %#v", err)) + } + os.Exit(1) + } + }() + + convert.RewritePackage(args[0]) +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/generate_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/generate_command.go new file mode 100644 index 000000000..99557048a --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/generate_command.go @@ -0,0 +1,256 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "text/template" +) + +func BuildGenerateCommand() *Command { + var agouti, noDot, internal bool + flagSet := flag.NewFlagSet("generate", flag.ExitOnError) + flagSet.BoolVar(&agouti, "agouti", false, "If set, generate will generate a test file for writing Agouti tests") + flagSet.BoolVar(&noDot, "nodot", false, "If set, generate will generate a test file that does not . import ginkgo and gomega") + flagSet.BoolVar(&internal, "internal", false, "If set, generate will generate a test file that uses the regular package name") + + return &Command{ + Name: "generate", + FlagSet: flagSet, + UsageCommand: "ginkgo generate ", + Usage: []string{ + "Generate a test file named filename_test.go", + "If the optional argument is omitted, a file named after the package in the current directory will be created.", + "Accepts the following flags:", + }, + Command: func(args []string, additionalArgs []string) { + generateSpec(args, agouti, noDot, internal) + }, + } +} + +var specText = `package {{.Package}} + +import ( + {{if .IncludeImports}}. "github.com/onsi/ginkgo"{{end}} + {{if .IncludeImports}}. "github.com/onsi/gomega"{{end}} + + {{if .ImportPackage}}"{{.PackageImportPath}}"{{end}} +) + +var _ = Describe("{{.Subject}}", func() { + +}) +` + +var agoutiSpecText = `package {{.Package}} + +import ( + {{if .IncludeImports}}. "github.com/onsi/ginkgo"{{end}} + {{if .IncludeImports}}. "github.com/onsi/gomega"{{end}} + "github.com/sclevine/agouti" + . "github.com/sclevine/agouti/matchers" + + {{if .ImportPackage}}"{{.PackageImportPath}}"{{end}} +) + +var _ = Describe("{{.Subject}}", func() { + var page *agouti.Page + + BeforeEach(func() { + var err error + page, err = agoutiDriver.NewPage() + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(page.Destroy()).To(Succeed()) + }) +}) +` + +type specData struct { + Package string + Subject string + PackageImportPath string + IncludeImports bool + ImportPackage bool +} + +func generateSpec(args []string, agouti, noDot, internal bool) { + if len(args) == 0 { + err := generateSpecForSubject("", agouti, noDot, internal) + if err != nil { + fmt.Println(err.Error()) + fmt.Println("") + os.Exit(1) + } + fmt.Println("") + return + } + + var failed bool + for _, arg := range args { + err := generateSpecForSubject(arg, agouti, noDot, internal) + if err != nil { + failed = true + fmt.Println(err.Error()) + } + } + fmt.Println("") + if failed { + os.Exit(1) + } +} + +func generateSpecForSubject(subject string, agouti, noDot, internal bool) error { + packageName, specFilePrefix, formattedName := getPackageAndFormattedName() + if subject != "" { + specFilePrefix = formatSubject(subject) + formattedName = prettifyPackageName(specFilePrefix) + } + + data := specData{ + Package: determinePackageName(packageName, internal), + Subject: formattedName, + PackageImportPath: getPackageImportPath(), + IncludeImports: !noDot, + ImportPackage: !internal, + } + + targetFile := fmt.Sprintf("%s_test.go", specFilePrefix) + if fileExists(targetFile) { + return fmt.Errorf("%s already exists.", targetFile) + } else { + fmt.Printf("Generating ginkgo test for %s in:\n %s\n", data.Subject, targetFile) + } + + f, err := os.Create(targetFile) + if err != nil { + return err + } + defer f.Close() + + var templateText string + if agouti { + templateText = agoutiSpecText + } else { + templateText = specText + } + + specTemplate, err := template.New("spec").Parse(templateText) + if err != nil { + return err + } + + specTemplate.Execute(f, data) + goFmt(targetFile) + return nil +} + +func formatSubject(name string) string { + name = strings.Replace(name, "-", "_", -1) + name = strings.Replace(name, " ", "_", -1) + name = strings.Split(name, ".go")[0] + name = strings.Split(name, "_test")[0] + return name +} + +// moduleName returns module name from go.mod from given module root directory +func moduleName(modRoot string) string { + modFile, err := os.Open(filepath.Join(modRoot, "go.mod")) + if err != nil { + return "" + } + + mod := make([]byte, 128) + _, err = modFile.Read(mod) + if err != nil { + return "" + } + + slashSlash := []byte("//") + moduleStr := []byte("module") + + for len(mod) > 0 { + line := mod + mod = nil + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, mod = line[:i], line[i+1:] + } + if i := bytes.Index(line, slashSlash); i >= 0 { + line = line[:i] + } + line = bytes.TrimSpace(line) + if !bytes.HasPrefix(line, moduleStr) { + continue + } + line = line[len(moduleStr):] + n := len(line) + line = bytes.TrimSpace(line) + if len(line) == n || len(line) == 0 { + continue + } + + if line[0] == '"' || line[0] == '`' { + p, err := strconv.Unquote(string(line)) + if err != nil { + return "" // malformed quoted string or multiline module path + } + return p + } + + return string(line) + } + + return "" // missing module path +} + +func findModuleRoot(dir string) (root string) { + dir = filepath.Clean(dir) + + // Look for enclosing go.mod. + for { + if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() { + return dir + } + d := filepath.Dir(dir) + if d == dir { + break + } + dir = d + } + return "" +} + +func getPackageImportPath() string { + workingDir, err := os.Getwd() + if err != nil { + panic(err.Error()) + } + + sep := string(filepath.Separator) + + // Try go.mod file first + modRoot := findModuleRoot(workingDir) + if modRoot != "" { + modName := moduleName(modRoot) + if modName != "" { + cd := strings.Replace(workingDir, modRoot, "", -1) + cd = strings.ReplaceAll(cd, sep, "/") + return modName + cd + } + } + + // Fallback to GOPATH structure + paths := strings.Split(workingDir, sep+"src"+sep) + if len(paths) == 1 { + fmt.Printf("\nCouldn't identify package import path.\n\n\tginkgo generate\n\nMust be run within a package directory under $GOPATH/src/...\nYou're going to have to change UNKNOWN_PACKAGE_PATH in the generated file...\n\n") + return "UNKNOWN_PACKAGE_PATH" + } + return filepath.ToSlash(paths[len(paths)-1]) +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/help_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/help_command.go new file mode 100644 index 000000000..23b1d2f11 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/help_command.go @@ -0,0 +1,31 @@ +package main + +import ( + "flag" + "fmt" +) + +func BuildHelpCommand() *Command { + return &Command{ + Name: "help", + FlagSet: flag.NewFlagSet("help", flag.ExitOnError), + UsageCommand: "ginkgo help ", + Usage: []string{ + "Print usage information. If a command is passed in, print usage information just for that command.", + }, + Command: printHelp, + } +} + +func printHelp(args []string, additionalArgs []string) { + if len(args) == 0 { + usage() + } else { + command, found := commandMatching(args[0]) + if !found { + complainAndQuit(fmt.Sprintf("Unknown command: %s", args[0])) + } + + usageForCommand(command, true) + } +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/interrupthandler/interrupt_handler.go b/vendor/github.com/onsi/ginkgo/ginkgo/interrupthandler/interrupt_handler.go new file mode 100644 index 000000000..ec456bf29 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/interrupthandler/interrupt_handler.go @@ -0,0 +1,52 @@ +package interrupthandler + +import ( + "os" + "os/signal" + "sync" + "syscall" +) + +type InterruptHandler struct { + interruptCount int + lock *sync.Mutex + C chan bool +} + +func NewInterruptHandler() *InterruptHandler { + h := &InterruptHandler{ + lock: &sync.Mutex{}, + C: make(chan bool), + } + + go h.handleInterrupt() + SwallowSigQuit() + + return h +} + +func (h *InterruptHandler) WasInterrupted() bool { + h.lock.Lock() + defer h.lock.Unlock() + + return h.interruptCount > 0 +} + +func (h *InterruptHandler) handleInterrupt() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + + <-c + signal.Stop(c) + + h.lock.Lock() + h.interruptCount++ + if h.interruptCount == 1 { + close(h.C) + } else if h.interruptCount > 5 { + os.Exit(1) + } + h.lock.Unlock() + + go h.handleInterrupt() +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/interrupthandler/sigquit_swallower_unix.go b/vendor/github.com/onsi/ginkgo/ginkgo/interrupthandler/sigquit_swallower_unix.go new file mode 100644 index 000000000..43c18544a --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/interrupthandler/sigquit_swallower_unix.go @@ -0,0 +1,14 @@ +// +build freebsd openbsd netbsd dragonfly darwin linux solaris + +package interrupthandler + +import ( + "os" + "os/signal" + "syscall" +) + +func SwallowSigQuit() { + c := make(chan os.Signal, 1024) + signal.Notify(c, syscall.SIGQUIT) +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/interrupthandler/sigquit_swallower_windows.go b/vendor/github.com/onsi/ginkgo/ginkgo/interrupthandler/sigquit_swallower_windows.go new file mode 100644 index 000000000..7f4a50e19 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/interrupthandler/sigquit_swallower_windows.go @@ -0,0 +1,7 @@ +// +build windows + +package interrupthandler + +func SwallowSigQuit() { + //noop +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/main.go b/vendor/github.com/onsi/ginkgo/ginkgo/main.go new file mode 100644 index 000000000..f60c48a72 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/main.go @@ -0,0 +1,302 @@ +/* +The Ginkgo CLI + +The Ginkgo CLI is fully documented [here](http://onsi.github.io/ginkgo/#the_ginkgo_cli) + +You can also learn more by running: + + ginkgo help + +Here are some of the more commonly used commands: + +To install: + + go install github.com/onsi/ginkgo/ginkgo + +To run tests: + + ginkgo + +To run tests in all subdirectories: + + ginkgo -r + +To run tests in particular packages: + + ginkgo /path/to/package /path/to/another/package + +To pass arguments/flags to your tests: + + ginkgo -- + +To run tests in parallel + + ginkgo -p + +this will automatically detect the optimal number of nodes to use. Alternatively, you can specify the number of nodes with: + + ginkgo -nodes=N + +(note that you don't need to provide -p in this case). + +By 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. +An 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: + + ginkgo -nodes=N -stream=true + +On windows, the default value for stream is true. + +By 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: + + ginkgo -keepGoing + +To fail if there are ginkgo tests in a directory but no test suite (missing `RunSpecs`) + + ginkgo -requireSuite + +To monitor packages and rerun tests when changes occur: + + ginkgo watch <-r> + +passing `ginkgo watch` the `-r` flag will recursively detect all test suites under the current directory and monitor them. +`watch` does not detect *new* packages. Moreover, changes in package X only rerun the tests for package X, tests for packages +that depend on X are not rerun. + +[OSX & Linux only] To receive (desktop) notifications when a test run completes: + + ginkgo -notify + +this is particularly useful with `ginkgo watch`. Notifications are currently only supported on OS X and require that you `brew install terminal-notifier` + +Sometimes (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: + + ginkgo -untilItFails + +To bootstrap a test suite: + + ginkgo bootstrap + +To generate a test file: + + ginkgo generate + +To bootstrap/generate test files without using "." imports: + + ginkgo bootstrap --nodot + ginkgo generate --nodot + +this 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 + + ginkgo nodot + +to refresh this list and pull in any new identifiers. In particular, this will pull in any new Gomega matchers that get added. + +To convert an existing XUnit style test suite to a Ginkgo-style test suite: + + ginkgo convert . + +To unfocus tests: + + ginkgo unfocus + +or + + ginkgo blur + +To compile a test suite: + + ginkgo build + +will output an executable file named `package.test`. This can be run directly or by invoking + + ginkgo + +To print out Ginkgo's version: + + ginkgo version + +To get more help: + + ginkgo help +*/ +package main + +import ( + "flag" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/onsi/ginkgo/config" + "github.com/onsi/ginkgo/ginkgo/testsuite" +) + +const greenColor = "\x1b[32m" +const redColor = "\x1b[91m" +const defaultStyle = "\x1b[0m" +const lightGrayColor = "\x1b[37m" + +type Command struct { + Name string + AltName string + FlagSet *flag.FlagSet + Usage []string + UsageCommand string + Command func(args []string, additionalArgs []string) + SuppressFlagDocumentation bool + FlagDocSubstitute []string +} + +func (c *Command) Matches(name string) bool { + return c.Name == name || (c.AltName != "" && c.AltName == name) +} + +func (c *Command) Run(args []string, additionalArgs []string) { + c.FlagSet.Usage = usage + c.FlagSet.Parse(args) + c.Command(c.FlagSet.Args(), additionalArgs) +} + +var DefaultCommand *Command +var Commands []*Command + +func init() { + DefaultCommand = BuildRunCommand() + Commands = append(Commands, BuildWatchCommand()) + Commands = append(Commands, BuildBuildCommand()) + Commands = append(Commands, BuildBootstrapCommand()) + Commands = append(Commands, BuildGenerateCommand()) + Commands = append(Commands, BuildNodotCommand()) + Commands = append(Commands, BuildConvertCommand()) + Commands = append(Commands, BuildUnfocusCommand()) + Commands = append(Commands, BuildVersionCommand()) + Commands = append(Commands, BuildHelpCommand()) +} + +func main() { + args := []string{} + additionalArgs := []string{} + + foundDelimiter := false + + for _, arg := range os.Args[1:] { + if !foundDelimiter { + if arg == "--" { + foundDelimiter = true + continue + } + } + + if foundDelimiter { + additionalArgs = append(additionalArgs, arg) + } else { + args = append(args, arg) + } + } + + if len(args) > 0 { + commandToRun, found := commandMatching(args[0]) + if found { + commandToRun.Run(args[1:], additionalArgs) + return + } + } + + DefaultCommand.Run(args, additionalArgs) +} + +func commandMatching(name string) (*Command, bool) { + for _, command := range Commands { + if command.Matches(name) { + return command, true + } + } + return nil, false +} + +func usage() { + fmt.Printf("Ginkgo Version %s\n\n", config.VERSION) + usageForCommand(DefaultCommand, false) + for _, command := range Commands { + fmt.Printf("\n") + usageForCommand(command, false) + } +} + +func usageForCommand(command *Command, longForm bool) { + fmt.Printf("%s\n%s\n", command.UsageCommand, strings.Repeat("-", len(command.UsageCommand))) + fmt.Printf("%s\n", strings.Join(command.Usage, "\n")) + if command.SuppressFlagDocumentation && !longForm { + fmt.Printf("%s\n", strings.Join(command.FlagDocSubstitute, "\n ")) + } else { + command.FlagSet.SetOutput(os.Stdout) + command.FlagSet.PrintDefaults() + } +} + +func complainAndQuit(complaint string) { + fmt.Fprintf(os.Stderr, "%s\nFor usage instructions:\n\tginkgo help\n", complaint) + os.Exit(1) +} + +func findSuites(args []string, recurseForAll bool, skipPackage string, allowPrecompiled bool) ([]testsuite.TestSuite, []string) { + suites := []testsuite.TestSuite{} + + if len(args) > 0 { + for _, arg := range args { + if allowPrecompiled { + suite, err := testsuite.PrecompiledTestSuite(arg) + if err == nil { + suites = append(suites, suite) + continue + } + } + recurseForSuite := recurseForAll + if strings.HasSuffix(arg, "/...") && arg != "/..." { + arg = arg[:len(arg)-4] + recurseForSuite = true + } + suites = append(suites, testsuite.SuitesInDir(arg, recurseForSuite)...) + } + } else { + suites = testsuite.SuitesInDir(".", recurseForAll) + } + + skippedPackages := []string{} + if skipPackage != "" { + skipFilters := strings.Split(skipPackage, ",") + filteredSuites := []testsuite.TestSuite{} + for _, suite := range suites { + skip := false + for _, skipFilter := range skipFilters { + if strings.Contains(suite.Path, skipFilter) { + skip = true + break + } + } + if skip { + skippedPackages = append(skippedPackages, suite.Path) + } else { + filteredSuites = append(filteredSuites, suite) + } + } + suites = filteredSuites + } + + return suites, skippedPackages +} + +func goFmt(path string) { + out, err := exec.Command("go", "fmt", path).CombinedOutput() + if err != nil { + complainAndQuit("Could not fmt: " + err.Error() + "\n" + string(out)) + } +} + +func pluralizedWord(singular, plural string, count int) string { + if count == 1 { + return singular + } + return plural +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/nodot/nodot.go b/vendor/github.com/onsi/ginkgo/ginkgo/nodot/nodot.go new file mode 100644 index 000000000..c87b72165 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/nodot/nodot.go @@ -0,0 +1,196 @@ +package nodot + +import ( + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "path/filepath" + "strings" +) + +func ApplyNoDot(data []byte) ([]byte, error) { + sections, err := generateNodotSections() + if err != nil { + return nil, err + } + + for _, section := range sections { + data = section.createOrUpdateIn(data) + } + + return data, nil +} + +type nodotSection struct { + name string + pkg string + declarations []string + types []string +} + +func (s nodotSection) createOrUpdateIn(data []byte) []byte { + renames := map[string]string{} + + contents := string(data) + + lines := strings.Split(contents, "\n") + + comment := "// Declarations for " + s.name + + newLines := []string{} + for _, line := range lines { + if line == comment { + continue + } + + words := strings.Split(line, " ") + lastWord := words[len(words)-1] + + if s.containsDeclarationOrType(lastWord) { + renames[lastWord] = words[1] + continue + } + + newLines = append(newLines, line) + } + + if len(newLines[len(newLines)-1]) > 0 { + newLines = append(newLines, "") + } + + newLines = append(newLines, comment) + + for _, typ := range s.types { + name, ok := renames[s.prefix(typ)] + if !ok { + name = typ + } + newLines = append(newLines, fmt.Sprintf("type %s %s", name, s.prefix(typ))) + } + + for _, decl := range s.declarations { + name, ok := renames[s.prefix(decl)] + if !ok { + name = decl + } + newLines = append(newLines, fmt.Sprintf("var %s = %s", name, s.prefix(decl))) + } + + newLines = append(newLines, "") + + newContents := strings.Join(newLines, "\n") + + return []byte(newContents) +} + +func (s nodotSection) prefix(declOrType string) string { + return s.pkg + "." + declOrType +} + +func (s nodotSection) containsDeclarationOrType(word string) bool { + for _, declaration := range s.declarations { + if s.prefix(declaration) == word { + return true + } + } + + for _, typ := range s.types { + if s.prefix(typ) == word { + return true + } + } + + return false +} + +func generateNodotSections() ([]nodotSection, error) { + sections := []nodotSection{} + + declarations, err := getExportedDeclerationsForPackage("github.com/onsi/ginkgo", "ginkgo_dsl.go", "GINKGO_VERSION", "GINKGO_PANIC") + if err != nil { + return nil, err + } + sections = append(sections, nodotSection{ + name: "Ginkgo DSL", + pkg: "ginkgo", + declarations: declarations, + types: []string{"Done", "Benchmarker"}, + }) + + declarations, err = getExportedDeclerationsForPackage("github.com/onsi/gomega", "gomega_dsl.go", "GOMEGA_VERSION") + if err != nil { + return nil, err + } + sections = append(sections, nodotSection{ + name: "Gomega DSL", + pkg: "gomega", + declarations: declarations, + }) + + declarations, err = getExportedDeclerationsForPackage("github.com/onsi/gomega", "matchers.go") + if err != nil { + return nil, err + } + sections = append(sections, nodotSection{ + name: "Gomega Matchers", + pkg: "gomega", + declarations: declarations, + }) + + return sections, nil +} + +func getExportedDeclerationsForPackage(pkgPath string, filename string, blacklist ...string) ([]string, error) { + pkg, err := build.Import(pkgPath, ".", 0) + if err != nil { + return []string{}, err + } + + declarations, err := getExportedDeclarationsForFile(filepath.Join(pkg.Dir, filename)) + if err != nil { + return []string{}, err + } + + blacklistLookup := map[string]bool{} + for _, declaration := range blacklist { + blacklistLookup[declaration] = true + } + + filteredDeclarations := []string{} + for _, declaration := range declarations { + if blacklistLookup[declaration] { + continue + } + filteredDeclarations = append(filteredDeclarations, declaration) + } + + return filteredDeclarations, nil +} + +func getExportedDeclarationsForFile(path string) ([]string, error) { + fset := token.NewFileSet() + tree, err := parser.ParseFile(fset, path, nil, 0) + if err != nil { + return []string{}, err + } + + declarations := []string{} + ast.FileExports(tree) + for _, decl := range tree.Decls { + switch x := decl.(type) { + case *ast.GenDecl: + switch s := x.Specs[0].(type) { + case *ast.ValueSpec: + declarations = append(declarations, s.Names[0].Name) + } + case *ast.FuncDecl: + if x.Recv == nil { + declarations = append(declarations, x.Name.Name) + } + } + } + + return declarations, nil +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/nodot_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/nodot_command.go new file mode 100644 index 000000000..39b88b5d1 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/nodot_command.go @@ -0,0 +1,77 @@ +package main + +import ( + "bufio" + "flag" + "io/ioutil" + "os" + "path/filepath" + "regexp" + + "github.com/onsi/ginkgo/ginkgo/nodot" +) + +func BuildNodotCommand() *Command { + return &Command{ + Name: "nodot", + FlagSet: flag.NewFlagSet("bootstrap", flag.ExitOnError), + UsageCommand: "ginkgo nodot", + Usage: []string{ + "Update the nodot declarations in your test suite", + "Any missing declarations (from, say, a recently added matcher) will be added to your bootstrap file.", + "If you've renamed a declaration, that name will be honored and not overwritten.", + }, + Command: updateNodot, + } +} + +func updateNodot(args []string, additionalArgs []string) { + suiteFile, perm := findSuiteFile() + + data, err := ioutil.ReadFile(suiteFile) + if err != nil { + complainAndQuit("Failed to update nodot declarations: " + err.Error()) + } + + content, err := nodot.ApplyNoDot(data) + if err != nil { + complainAndQuit("Failed to update nodot declarations: " + err.Error()) + } + ioutil.WriteFile(suiteFile, content, perm) + + goFmt(suiteFile) +} + +func findSuiteFile() (string, os.FileMode) { + workingDir, err := os.Getwd() + if err != nil { + complainAndQuit("Could not find suite file for nodot: " + err.Error()) + } + + files, err := ioutil.ReadDir(workingDir) + if err != nil { + complainAndQuit("Could not find suite file for nodot: " + err.Error()) + } + + re := regexp.MustCompile(`RunSpecs\(|RunSpecsWithDefaultAndCustomReporters\(|RunSpecsWithCustomReporters\(`) + + for _, file := range files { + if file.IsDir() { + continue + } + path := filepath.Join(workingDir, file.Name()) + f, err := os.Open(path) + if err != nil { + complainAndQuit("Could not find suite file for nodot: " + err.Error()) + } + defer f.Close() + + if re.MatchReader(bufio.NewReader(f)) { + return path, file.Mode() + } + } + + complainAndQuit("Could not find a suite file for nodot: you need a bootstrap file that call's Ginkgo's RunSpecs() command.\nTry running ginkgo bootstrap first.") + + return "", 0 +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/notifications.go b/vendor/github.com/onsi/ginkgo/ginkgo/notifications.go new file mode 100644 index 000000000..368d61fb3 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/notifications.go @@ -0,0 +1,141 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "regexp" + "runtime" + "strings" + + "github.com/onsi/ginkgo/config" + "github.com/onsi/ginkgo/ginkgo/testsuite" +) + +type Notifier struct { + commandFlags *RunWatchAndBuildCommandFlags +} + +func NewNotifier(commandFlags *RunWatchAndBuildCommandFlags) *Notifier { + return &Notifier{ + commandFlags: commandFlags, + } +} + +func (n *Notifier) VerifyNotificationsAreAvailable() { + if n.commandFlags.Notify { + onLinux := (runtime.GOOS == "linux") + onOSX := (runtime.GOOS == "darwin") + if onOSX { + + _, err := exec.LookPath("terminal-notifier") + if err != nil { + fmt.Printf(`--notify requires terminal-notifier, which you don't seem to have installed. + +OSX: + +To remedy this: + + brew install terminal-notifier + +To learn more about terminal-notifier: + + https://github.com/alloy/terminal-notifier +`) + os.Exit(1) + } + + } else if onLinux { + + _, err := exec.LookPath("notify-send") + if err != nil { + fmt.Printf(`--notify requires terminal-notifier or notify-send, which you don't seem to have installed. + +Linux: + +Download and install notify-send for your distribution +`) + os.Exit(1) + } + + } + } +} + +func (n *Notifier) SendSuiteCompletionNotification(suite testsuite.TestSuite, suitePassed bool) { + if suitePassed { + n.SendNotification("Ginkgo [PASS]", fmt.Sprintf(`Test suite for "%s" passed.`, suite.PackageName)) + } else { + n.SendNotification("Ginkgo [FAIL]", fmt.Sprintf(`Test suite for "%s" failed.`, suite.PackageName)) + } +} + +func (n *Notifier) SendNotification(title string, subtitle string) { + + if n.commandFlags.Notify { + onLinux := (runtime.GOOS == "linux") + onOSX := (runtime.GOOS == "darwin") + + if onOSX { + + _, err := exec.LookPath("terminal-notifier") + if err == nil { + args := []string{"-title", title, "-subtitle", subtitle, "-group", "com.onsi.ginkgo"} + terminal := os.Getenv("TERM_PROGRAM") + if terminal == "iTerm.app" { + args = append(args, "-activate", "com.googlecode.iterm2") + } else if terminal == "Apple_Terminal" { + args = append(args, "-activate", "com.apple.Terminal") + } + + exec.Command("terminal-notifier", args...).Run() + } + + } else if onLinux { + + _, err := exec.LookPath("notify-send") + if err == nil { + args := []string{"-a", "ginkgo", title, subtitle} + exec.Command("notify-send", args...).Run() + } + + } + } +} + +func (n *Notifier) RunCommand(suite testsuite.TestSuite, suitePassed bool) { + + command := n.commandFlags.AfterSuiteHook + if command != "" { + + // Allow for string replacement to pass input to the command + passed := "[FAIL]" + if suitePassed { + passed = "[PASS]" + } + command = strings.Replace(command, "(ginkgo-suite-passed)", passed, -1) + command = strings.Replace(command, "(ginkgo-suite-name)", suite.PackageName, -1) + + // Must break command into parts + splitArgs := regexp.MustCompile(`'.+'|".+"|\S+`) + parts := splitArgs.FindAllString(command, -1) + + output, err := exec.Command(parts[0], parts[1:]...).CombinedOutput() + if err != nil { + fmt.Println("Post-suite command failed:") + if config.DefaultReporterConfig.NoColor { + fmt.Printf("\t%s\n", output) + } else { + fmt.Printf("\t%s%s%s\n", redColor, string(output), defaultStyle) + } + n.SendNotification("Ginkgo [ERROR]", fmt.Sprintf(`After suite command "%s" failed`, n.commandFlags.AfterSuiteHook)) + } else { + fmt.Println("Post-suite command succeeded:") + if config.DefaultReporterConfig.NoColor { + fmt.Printf("\t%s\n", output) + } else { + fmt.Printf("\t%s%s%s\n", greenColor, string(output), defaultStyle) + } + } + } +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/run_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/run_command.go new file mode 100644 index 000000000..f225d272f --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/run_command.go @@ -0,0 +1,291 @@ +package main + +import ( + "flag" + "fmt" + "math/rand" + "os" + "regexp" + "strings" + "time" + + "io/ioutil" + "path/filepath" + + "github.com/onsi/ginkgo/config" + "github.com/onsi/ginkgo/ginkgo/interrupthandler" + "github.com/onsi/ginkgo/ginkgo/testrunner" + "github.com/onsi/ginkgo/types" +) + +func BuildRunCommand() *Command { + commandFlags := NewRunCommandFlags(flag.NewFlagSet("ginkgo", flag.ExitOnError)) + notifier := NewNotifier(commandFlags) + interruptHandler := interrupthandler.NewInterruptHandler() + runner := &SpecRunner{ + commandFlags: commandFlags, + notifier: notifier, + interruptHandler: interruptHandler, + suiteRunner: NewSuiteRunner(notifier, interruptHandler), + } + + return &Command{ + Name: "", + FlagSet: commandFlags.FlagSet, + UsageCommand: "ginkgo -- ", + Usage: []string{ + "Run the tests in the passed in (or the package in the current directory if left blank).", + "Any arguments after -- will be passed to the test.", + "Accepts the following flags:", + }, + Command: runner.RunSpecs, + } +} + +type SpecRunner struct { + commandFlags *RunWatchAndBuildCommandFlags + notifier *Notifier + interruptHandler *interrupthandler.InterruptHandler + suiteRunner *SuiteRunner +} + +func (r *SpecRunner) RunSpecs(args []string, additionalArgs []string) { + r.commandFlags.computeNodes() + r.notifier.VerifyNotificationsAreAvailable() + + suites, skippedPackages := findSuites(args, r.commandFlags.Recurse, r.commandFlags.SkipPackage, true) + if len(skippedPackages) > 0 { + fmt.Println("Will skip:") + for _, skippedPackage := range skippedPackages { + fmt.Println(" " + skippedPackage) + } + } + + if len(skippedPackages) > 0 && len(suites) == 0 { + fmt.Println("All tests skipped! Exiting...") + os.Exit(0) + } + + if len(suites) == 0 { + complainAndQuit("Found no test suites") + } + + r.ComputeSuccinctMode(len(suites)) + + t := time.Now() + + runners := []*testrunner.TestRunner{} + for _, suite := range suites { + runners = append(runners, testrunner.New(suite, r.commandFlags.NumCPU, r.commandFlags.ParallelStream, r.commandFlags.Timeout, r.commandFlags.GoOpts, additionalArgs)) + } + + numSuites := 0 + runResult := testrunner.PassingRunResult() + if r.commandFlags.UntilItFails { + iteration := 0 + for { + r.UpdateSeed() + randomizedRunners := r.randomizeOrder(runners) + runResult, numSuites = r.suiteRunner.RunSuites(randomizedRunners, r.commandFlags.NumCompilers, r.commandFlags.KeepGoing, nil) + iteration++ + + if r.interruptHandler.WasInterrupted() { + break + } + + if runResult.Passed { + fmt.Printf("\nAll tests passed...\nWill keep running them until they fail.\nThis was attempt #%d\n%s\n", iteration, orcMessage(iteration)) + } else { + fmt.Printf("\nTests failed on attempt #%d\n\n", iteration) + break + } + } + } else { + randomizedRunners := r.randomizeOrder(runners) + runResult, numSuites = r.suiteRunner.RunSuites(randomizedRunners, r.commandFlags.NumCompilers, r.commandFlags.KeepGoing, nil) + } + + for _, runner := range runners { + runner.CleanUp() + } + + if r.isInCoverageMode() { + if r.getOutputDir() != "" { + // If coverprofile is set, combine coverages + if r.getCoverprofile() != "" { + if err := r.combineCoverprofiles(runners); err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + } else { + // Just move them + r.moveCoverprofiles(runners) + } + } + } + + fmt.Printf("\nGinkgo ran %d %s in %s\n", numSuites, pluralizedWord("suite", "suites", numSuites), time.Since(t)) + + if runResult.Passed { + if runResult.HasProgrammaticFocus && strings.TrimSpace(os.Getenv("GINKGO_EDITOR_INTEGRATION")) == "" { + fmt.Printf("Test Suite Passed\n") + fmt.Printf("Detected Programmatic Focus - setting exit status to %d\n", types.GINKGO_FOCUS_EXIT_CODE) + os.Exit(types.GINKGO_FOCUS_EXIT_CODE) + } else { + fmt.Printf("Test Suite Passed\n") + os.Exit(0) + } + } else { + fmt.Printf("Test Suite Failed\n") + os.Exit(1) + } +} + +// Moves all generated profiles to specified directory +func (r *SpecRunner) moveCoverprofiles(runners []*testrunner.TestRunner) { + for _, runner := range runners { + _, filename := filepath.Split(runner.CoverageFile) + err := os.Rename(runner.CoverageFile, filepath.Join(r.getOutputDir(), filename)) + + if err != nil { + fmt.Printf("Unable to move coverprofile %s, %v\n", runner.CoverageFile, err) + return + } + } +} + +// Combines all generated profiles in the specified directory +func (r *SpecRunner) combineCoverprofiles(runners []*testrunner.TestRunner) error { + + path, _ := filepath.Abs(r.getOutputDir()) + if !fileExists(path) { + return fmt.Errorf("Unable to create combined profile, outputdir does not exist: %s", r.getOutputDir()) + } + + fmt.Println("path is " + path) + + combined, err := os.OpenFile( + filepath.Join(path, r.getCoverprofile()), + os.O_WRONLY|os.O_CREATE, + 0666, + ) + + if err != nil { + fmt.Printf("Unable to create combined profile, %v\n", err) + return nil // non-fatal error + } + + modeRegex := regexp.MustCompile(`^mode: .*\n`) + for index, runner := range runners { + contents, err := ioutil.ReadFile(runner.CoverageFile) + + if err != nil { + fmt.Printf("Unable to read coverage file %s to combine, %v\n", runner.CoverageFile, err) + return nil // non-fatal error + } + + // remove the cover mode line from every file + // except the first one + if index > 0 { + contents = modeRegex.ReplaceAll(contents, []byte{}) + } + + _, err = combined.Write(contents) + + // Add a newline to the end of every file if missing. + if err == nil && len(contents) > 0 && contents[len(contents)-1] != '\n' { + _, err = combined.Write([]byte("\n")) + } + + if err != nil { + fmt.Printf("Unable to append to coverprofile, %v\n", err) + return nil // non-fatal error + } + } + + fmt.Println("All profiles combined") + return nil +} + +func (r *SpecRunner) isInCoverageMode() bool { + opts := r.commandFlags.GoOpts + return *opts["cover"].(*bool) || *opts["coverpkg"].(*string) != "" || *opts["covermode"].(*string) != "" +} + +func (r *SpecRunner) getCoverprofile() string { + return *r.commandFlags.GoOpts["coverprofile"].(*string) +} + +func (r *SpecRunner) getOutputDir() string { + return *r.commandFlags.GoOpts["outputdir"].(*string) +} + +func (r *SpecRunner) ComputeSuccinctMode(numSuites int) { + if config.DefaultReporterConfig.Verbose { + config.DefaultReporterConfig.Succinct = false + return + } + + if numSuites == 1 { + return + } + + if numSuites > 1 && !r.commandFlags.wasSet("succinct") { + config.DefaultReporterConfig.Succinct = true + } +} + +func (r *SpecRunner) UpdateSeed() { + if !r.commandFlags.wasSet("seed") { + config.GinkgoConfig.RandomSeed = time.Now().Unix() + } +} + +func (r *SpecRunner) randomizeOrder(runners []*testrunner.TestRunner) []*testrunner.TestRunner { + if !r.commandFlags.RandomizeSuites { + return runners + } + + if len(runners) <= 1 { + return runners + } + + randomizedRunners := make([]*testrunner.TestRunner, len(runners)) + randomizer := rand.New(rand.NewSource(config.GinkgoConfig.RandomSeed)) + permutation := randomizer.Perm(len(runners)) + for i, j := range permutation { + randomizedRunners[i] = runners[j] + } + return randomizedRunners +} + +func orcMessage(iteration int) string { + if iteration < 10 { + return "" + } else if iteration < 30 { + return []string{ + "If at first you succeed...", + "...try, try again.", + "Looking good!", + "Still good...", + "I think your tests are fine....", + "Yep, still passing", + "Oh boy, here I go testin' again!", + "Even the gophers are getting bored", + "Did you try -race?", + "Maybe you should stop now?", + "I'm getting tired...", + "What if I just made you a sandwich?", + "Hit ^C, hit ^C, please hit ^C", + "Make it stop. Please!", + "Come on! Enough is enough!", + "Dave, this conversation can serve no purpose anymore. Goodbye.", + "Just what do you think you're doing, Dave? ", + "I, Sisyphus", + "Insanity: doing the same thing over and over again and expecting different results. -Einstein", + "I guess Einstein never tried to churn butter", + }[iteration-10] + "\n" + } else { + return "No, seriously... you can probably stop now.\n" + } +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/run_watch_and_build_command_flags.go b/vendor/github.com/onsi/ginkgo/ginkgo/run_watch_and_build_command_flags.go new file mode 100644 index 000000000..e0994fc3c --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/run_watch_and_build_command_flags.go @@ -0,0 +1,169 @@ +package main + +import ( + "flag" + "runtime" + + "time" + + "github.com/onsi/ginkgo/config" +) + +type RunWatchAndBuildCommandFlags struct { + Recurse bool + SkipPackage string + GoOpts map[string]interface{} + + //for run and watch commands + NumCPU int + NumCompilers int + ParallelStream bool + Notify bool + AfterSuiteHook string + AutoNodes bool + Timeout time.Duration + + //only for run command + KeepGoing bool + UntilItFails bool + RandomizeSuites bool + + //only for watch command + Depth int + WatchRegExp string + + FlagSet *flag.FlagSet +} + +const runMode = 1 +const watchMode = 2 +const buildMode = 3 + +func NewRunCommandFlags(flagSet *flag.FlagSet) *RunWatchAndBuildCommandFlags { + c := &RunWatchAndBuildCommandFlags{ + FlagSet: flagSet, + } + c.flags(runMode) + return c +} + +func NewWatchCommandFlags(flagSet *flag.FlagSet) *RunWatchAndBuildCommandFlags { + c := &RunWatchAndBuildCommandFlags{ + FlagSet: flagSet, + } + c.flags(watchMode) + return c +} + +func NewBuildCommandFlags(flagSet *flag.FlagSet) *RunWatchAndBuildCommandFlags { + c := &RunWatchAndBuildCommandFlags{ + FlagSet: flagSet, + } + c.flags(buildMode) + return c +} + +func (c *RunWatchAndBuildCommandFlags) wasSet(flagName string) bool { + wasSet := false + c.FlagSet.Visit(func(f *flag.Flag) { + if f.Name == flagName { + wasSet = true + } + }) + + return wasSet +} + +func (c *RunWatchAndBuildCommandFlags) computeNodes() { + if c.wasSet("nodes") { + return + } + if c.AutoNodes { + switch n := runtime.NumCPU(); { + case n <= 4: + c.NumCPU = n + default: + c.NumCPU = n - 1 + } + } +} + +func (c *RunWatchAndBuildCommandFlags) stringSlot(slot string) *string { + var opt string + c.GoOpts[slot] = &opt + return &opt +} + +func (c *RunWatchAndBuildCommandFlags) boolSlot(slot string) *bool { + var opt bool + c.GoOpts[slot] = &opt + return &opt +} + +func (c *RunWatchAndBuildCommandFlags) intSlot(slot string) *int { + var opt int + c.GoOpts[slot] = &opt + return &opt +} + +func (c *RunWatchAndBuildCommandFlags) flags(mode int) { + c.GoOpts = make(map[string]interface{}) + + onWindows := (runtime.GOOS == "windows") + + c.FlagSet.BoolVar(&(c.Recurse), "r", false, "Find and run test suites under the current directory recursively.") + c.FlagSet.BoolVar(c.boolSlot("race"), "race", false, "Run tests with race detection enabled.") + c.FlagSet.BoolVar(c.boolSlot("cover"), "cover", false, "Run tests with coverage analysis, will generate coverage profiles with the package name in the current directory.") + c.FlagSet.StringVar(c.stringSlot("coverpkg"), "coverpkg", "", "Run tests with coverage on the given external modules.") + c.FlagSet.StringVar(&(c.SkipPackage), "skipPackage", "", "A comma-separated list of package names to be skipped. If any part of the package's path matches, that package is ignored.") + c.FlagSet.StringVar(c.stringSlot("tags"), "tags", "", "A list of build tags to consider satisfied during the build.") + c.FlagSet.StringVar(c.stringSlot("gcflags"), "gcflags", "", "Arguments to pass on each go tool compile invocation.") + c.FlagSet.StringVar(c.stringSlot("covermode"), "covermode", "", "Set the mode for coverage analysis.") + c.FlagSet.BoolVar(c.boolSlot("a"), "a", false, "Force rebuilding of packages that are already up-to-date.") + c.FlagSet.BoolVar(c.boolSlot("n"), "n", false, "Have `go test` print the commands but do not run them.") + c.FlagSet.BoolVar(c.boolSlot("msan"), "msan", false, "Enable interoperation with memory sanitizer.") + c.FlagSet.BoolVar(c.boolSlot("x"), "x", false, "Have `go test` print the commands.") + c.FlagSet.BoolVar(c.boolSlot("work"), "work", false, "Print the name of the temporary work directory and do not delete it when exiting.") + c.FlagSet.StringVar(c.stringSlot("asmflags"), "asmflags", "", "Arguments to pass on each go tool asm invocation.") + c.FlagSet.StringVar(c.stringSlot("buildmode"), "buildmode", "", "Build mode to use. See 'go help buildmode' for more.") + c.FlagSet.StringVar(c.stringSlot("mod"), "mod", "", "Go module control. See 'go help modules' for more.") + c.FlagSet.StringVar(c.stringSlot("compiler"), "compiler", "", "Name of compiler to use, as in runtime.Compiler (gccgo or gc).") + c.FlagSet.StringVar(c.stringSlot("gccgoflags"), "gccgoflags", "", "Arguments to pass on each gccgo compiler/linker invocation.") + c.FlagSet.StringVar(c.stringSlot("installsuffix"), "installsuffix", "", "A suffix to use in the name of the package installation directory.") + c.FlagSet.StringVar(c.stringSlot("ldflags"), "ldflags", "", "Arguments to pass on each go tool link invocation.") + c.FlagSet.BoolVar(c.boolSlot("linkshared"), "linkshared", false, "Link against shared libraries previously created with -buildmode=shared.") + c.FlagSet.StringVar(c.stringSlot("pkgdir"), "pkgdir", "", "install and load all packages from the given dir instead of the usual locations.") + c.FlagSet.StringVar(c.stringSlot("toolexec"), "toolexec", "", "a program to use to invoke toolchain programs like vet and asm.") + c.FlagSet.IntVar(c.intSlot("blockprofilerate"), "blockprofilerate", 1, "Control the detail provided in goroutine blocking profiles by calling runtime.SetBlockProfileRate with the given value.") + c.FlagSet.StringVar(c.stringSlot("coverprofile"), "coverprofile", "", "Write a coverage profile to the specified file after all tests have passed.") + c.FlagSet.StringVar(c.stringSlot("cpuprofile"), "cpuprofile", "", "Write a CPU profile to the specified file before exiting.") + c.FlagSet.StringVar(c.stringSlot("memprofile"), "memprofile", "", "Write a memory profile to the specified file after all tests have passed.") + c.FlagSet.IntVar(c.intSlot("memprofilerate"), "memprofilerate", 0, "Enable more precise (and expensive) memory profiles by setting runtime.MemProfileRate.") + c.FlagSet.StringVar(c.stringSlot("outputdir"), "outputdir", "", "Place output files from profiling in the specified directory.") + c.FlagSet.BoolVar(c.boolSlot("requireSuite"), "requireSuite", false, "Fail if there are ginkgo tests in a directory but no test suite (missing RunSpecs)") + c.FlagSet.StringVar(c.stringSlot("vet"), "vet", "", "Configure the invocation of 'go vet' to use the comma-separated list of vet checks. If list is 'off', 'go test' does not run 'go vet' at all.") + + if mode == runMode || mode == watchMode { + config.Flags(c.FlagSet, "", false) + c.FlagSet.IntVar(&(c.NumCPU), "nodes", 1, "The number of parallel test nodes to run") + c.FlagSet.IntVar(&(c.NumCompilers), "compilers", 0, "The number of concurrent compilations to run (0 will autodetect)") + c.FlagSet.BoolVar(&(c.AutoNodes), "p", false, "Run in parallel with auto-detected number of nodes") + c.FlagSet.BoolVar(&(c.ParallelStream), "stream", onWindows, "stream parallel test output in real time: less coherent, but useful for debugging") + if !onWindows { + c.FlagSet.BoolVar(&(c.Notify), "notify", false, "Send desktop notifications when a test run completes") + } + c.FlagSet.StringVar(&(c.AfterSuiteHook), "afterSuiteHook", "", "Run a command when a suite test run completes") + c.FlagSet.DurationVar(&(c.Timeout), "timeout", 24*time.Hour, "Suite fails if it does not complete within the specified timeout") + } + + if mode == runMode { + c.FlagSet.BoolVar(&(c.KeepGoing), "keepGoing", false, "When true, failures from earlier test suites do not prevent later test suites from running") + c.FlagSet.BoolVar(&(c.UntilItFails), "untilItFails", false, "When true, Ginkgo will keep rerunning tests until a failure occurs") + c.FlagSet.BoolVar(&(c.RandomizeSuites), "randomizeSuites", false, "When true, Ginkgo will randomize the order in which test suites run") + } + + if mode == watchMode { + c.FlagSet.IntVar(&(c.Depth), "depth", 1, "Ginkgo will watch dependencies down to this depth in the dependency tree") + c.FlagSet.StringVar(&(c.WatchRegExp), "watchRegExp", `\.go$`, "Files matching this regular expression will be watched for changes") + } +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/suite_runner.go b/vendor/github.com/onsi/ginkgo/ginkgo/suite_runner.go new file mode 100644 index 000000000..ab746d7e9 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/suite_runner.go @@ -0,0 +1,173 @@ +package main + +import ( + "fmt" + "runtime" + "sync" + + "github.com/onsi/ginkgo/config" + "github.com/onsi/ginkgo/ginkgo/interrupthandler" + "github.com/onsi/ginkgo/ginkgo/testrunner" + "github.com/onsi/ginkgo/ginkgo/testsuite" + colorable "github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable" +) + +type compilationInput struct { + runner *testrunner.TestRunner + result chan compilationOutput +} + +type compilationOutput struct { + runner *testrunner.TestRunner + err error +} + +type SuiteRunner struct { + notifier *Notifier + interruptHandler *interrupthandler.InterruptHandler +} + +func NewSuiteRunner(notifier *Notifier, interruptHandler *interrupthandler.InterruptHandler) *SuiteRunner { + return &SuiteRunner{ + notifier: notifier, + interruptHandler: interruptHandler, + } +} + +func (r *SuiteRunner) compileInParallel(runners []*testrunner.TestRunner, numCompilers int, willCompile func(suite testsuite.TestSuite)) chan compilationOutput { + //we return this to the consumer, it will return each runner in order as it compiles + compilationOutputs := make(chan compilationOutput, len(runners)) + + //an array of channels - the nth runner's compilation output is sent to the nth channel in this array + //we read from these channels in order to ensure we run the suites in order + orderedCompilationOutputs := []chan compilationOutput{} + for range runners { + orderedCompilationOutputs = append(orderedCompilationOutputs, make(chan compilationOutput, 1)) + } + + //we're going to spin up numCompilers compilers - they're going to run concurrently and will consume this channel + //we prefill the channel then close it, this ensures we compile things in the correct order + workPool := make(chan compilationInput, len(runners)) + for i, runner := range runners { + workPool <- compilationInput{runner, orderedCompilationOutputs[i]} + } + close(workPool) + + //pick a reasonable numCompilers + if numCompilers == 0 { + numCompilers = runtime.NumCPU() + } + + //a WaitGroup to help us wait for all compilers to shut down + wg := &sync.WaitGroup{} + wg.Add(numCompilers) + + //spin up the concurrent compilers + for i := 0; i < numCompilers; i++ { + go func() { + defer wg.Done() + for input := range workPool { + if r.interruptHandler.WasInterrupted() { + return + } + + if willCompile != nil { + willCompile(input.runner.Suite) + } + + //We retry because Go sometimes steps on itself when multiple compiles happen in parallel. This is ugly, but should help resolve flakiness... + var err error + retries := 0 + for retries <= 5 { + if r.interruptHandler.WasInterrupted() { + return + } + if err = input.runner.Compile(); err == nil { + break + } + retries++ + } + + input.result <- compilationOutput{input.runner, err} + } + }() + } + + //read from the compilation output channels *in order* and send them to the caller + //close the compilationOutputs channel to tell the caller we're done + go func() { + defer close(compilationOutputs) + for _, orderedCompilationOutput := range orderedCompilationOutputs { + select { + case compilationOutput := <-orderedCompilationOutput: + compilationOutputs <- compilationOutput + case <-r.interruptHandler.C: + //interrupt detected, wait for the compilers to shut down then bail + //this ensure we clean up after ourselves as we don't leave any compilation processes running + wg.Wait() + return + } + } + }() + + return compilationOutputs +} + +func (r *SuiteRunner) RunSuites(runners []*testrunner.TestRunner, numCompilers int, keepGoing bool, willCompile func(suite testsuite.TestSuite)) (testrunner.RunResult, int) { + runResult := testrunner.PassingRunResult() + + compilationOutputs := r.compileInParallel(runners, numCompilers, willCompile) + + numSuitesThatRan := 0 + suitesThatFailed := []testsuite.TestSuite{} + for compilationOutput := range compilationOutputs { + if compilationOutput.err != nil { + fmt.Print(compilationOutput.err.Error()) + } + numSuitesThatRan++ + suiteRunResult := testrunner.FailingRunResult() + if compilationOutput.err == nil { + suiteRunResult = compilationOutput.runner.Run() + } + r.notifier.SendSuiteCompletionNotification(compilationOutput.runner.Suite, suiteRunResult.Passed) + r.notifier.RunCommand(compilationOutput.runner.Suite, suiteRunResult.Passed) + runResult = runResult.Merge(suiteRunResult) + if !suiteRunResult.Passed { + suitesThatFailed = append(suitesThatFailed, compilationOutput.runner.Suite) + if !keepGoing { + break + } + } + if numSuitesThatRan < len(runners) && !config.DefaultReporterConfig.Succinct { + fmt.Println("") + } + } + + if keepGoing && !runResult.Passed { + r.listFailedSuites(suitesThatFailed) + } + + return runResult, numSuitesThatRan +} + +func (r *SuiteRunner) listFailedSuites(suitesThatFailed []testsuite.TestSuite) { + fmt.Println("") + fmt.Println("There were failures detected in the following suites:") + + maxPackageNameLength := 0 + for _, suite := range suitesThatFailed { + if len(suite.PackageName) > maxPackageNameLength { + maxPackageNameLength = len(suite.PackageName) + } + } + + packageNameFormatter := fmt.Sprintf("%%%ds", maxPackageNameLength) + + for _, suite := range suitesThatFailed { + if config.DefaultReporterConfig.NoColor { + fmt.Printf("\t"+packageNameFormatter+" %s\n", suite.PackageName, suite.Path) + } else { + fmt.Fprintf(colorable.NewColorableStdout(), "\t%s"+packageNameFormatter+"%s %s%s%s\n", redColor, suite.PackageName, defaultStyle, lightGrayColor, suite.Path, defaultStyle) + } + } +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/build_args.go b/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/build_args.go new file mode 100644 index 000000000..3b1a238c2 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/build_args.go @@ -0,0 +1,7 @@ +// +build go1.10 + +package testrunner + +var ( + buildArgs = []string{"test", "-c"} +) diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/build_args_old.go b/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/build_args_old.go new file mode 100644 index 000000000..14d70dbcc --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/build_args_old.go @@ -0,0 +1,7 @@ +// +build !go1.10 + +package testrunner + +var ( + buildArgs = []string{"test", "-c", "-i"} +) diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/log_writer.go b/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/log_writer.go new file mode 100644 index 000000000..a73a6e379 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/log_writer.go @@ -0,0 +1,52 @@ +package testrunner + +import ( + "bytes" + "fmt" + "io" + "log" + "strings" + "sync" +) + +type logWriter struct { + buffer *bytes.Buffer + lock *sync.Mutex + log *log.Logger +} + +func newLogWriter(target io.Writer, node int) *logWriter { + return &logWriter{ + buffer: &bytes.Buffer{}, + lock: &sync.Mutex{}, + log: log.New(target, fmt.Sprintf("[%d] ", node), 0), + } +} + +func (w *logWriter) Write(data []byte) (n int, err error) { + w.lock.Lock() + defer w.lock.Unlock() + + w.buffer.Write(data) + contents := w.buffer.String() + + lines := strings.Split(contents, "\n") + for _, line := range lines[0 : len(lines)-1] { + w.log.Println(line) + } + + w.buffer.Reset() + w.buffer.Write([]byte(lines[len(lines)-1])) + return len(data), nil +} + +func (w *logWriter) Close() error { + w.lock.Lock() + defer w.lock.Unlock() + + if w.buffer.Len() > 0 { + w.log.Println(w.buffer.String()) + } + + return nil +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/run_result.go b/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/run_result.go new file mode 100644 index 000000000..5d472acb8 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/run_result.go @@ -0,0 +1,27 @@ +package testrunner + +type RunResult struct { + Passed bool + HasProgrammaticFocus bool +} + +func PassingRunResult() RunResult { + return RunResult{ + Passed: true, + HasProgrammaticFocus: false, + } +} + +func FailingRunResult() RunResult { + return RunResult{ + Passed: false, + HasProgrammaticFocus: false, + } +} + +func (r RunResult) Merge(o RunResult) RunResult { + return RunResult{ + Passed: r.Passed && o.Passed, + HasProgrammaticFocus: r.HasProgrammaticFocus || o.HasProgrammaticFocus, + } +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/test_runner.go b/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/test_runner.go new file mode 100644 index 000000000..66c0f06f6 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/testrunner/test_runner.go @@ -0,0 +1,554 @@ +package testrunner + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "syscall" + "time" + + "github.com/onsi/ginkgo/config" + "github.com/onsi/ginkgo/ginkgo/testsuite" + "github.com/onsi/ginkgo/internal/remote" + "github.com/onsi/ginkgo/reporters/stenographer" + colorable "github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable" + "github.com/onsi/ginkgo/types" +) + +type TestRunner struct { + Suite testsuite.TestSuite + + compiled bool + compilationTargetPath string + + numCPU int + parallelStream bool + timeout time.Duration + goOpts map[string]interface{} + additionalArgs []string + stderr *bytes.Buffer + + CoverageFile string +} + +func New(suite testsuite.TestSuite, numCPU int, parallelStream bool, timeout time.Duration, goOpts map[string]interface{}, additionalArgs []string) *TestRunner { + runner := &TestRunner{ + Suite: suite, + numCPU: numCPU, + parallelStream: parallelStream, + goOpts: goOpts, + additionalArgs: additionalArgs, + timeout: timeout, + stderr: new(bytes.Buffer), + } + + if !suite.Precompiled { + runner.compilationTargetPath, _ = filepath.Abs(filepath.Join(suite.Path, suite.PackageName+".test")) + } + + return runner +} + +func (t *TestRunner) Compile() error { + return t.CompileTo(t.compilationTargetPath) +} + +func (t *TestRunner) BuildArgs(path string) []string { + args := make([]string, len(buildArgs), len(buildArgs)+3) + copy(args, buildArgs) + args = append(args, "-o", path, t.Suite.Path) + + if t.getCoverMode() != "" { + args = append(args, "-cover", fmt.Sprintf("-covermode=%s", t.getCoverMode())) + } else { + if t.shouldCover() || t.getCoverPackage() != "" { + args = append(args, "-cover", "-covermode=atomic") + } + } + + boolOpts := []string{ + "a", + "n", + "msan", + "race", + "x", + "work", + "linkshared", + } + + for _, opt := range boolOpts { + if s, found := t.goOpts[opt].(*bool); found && *s { + args = append(args, fmt.Sprintf("-%s", opt)) + } + } + + intOpts := []string{ + "memprofilerate", + "blockprofilerate", + } + + for _, opt := range intOpts { + if s, found := t.goOpts[opt].(*int); found { + args = append(args, fmt.Sprintf("-%s=%d", opt, *s)) + } + } + + stringOpts := []string{ + "asmflags", + "buildmode", + "compiler", + "gccgoflags", + "installsuffix", + "ldflags", + "pkgdir", + "toolexec", + "coverprofile", + "cpuprofile", + "memprofile", + "outputdir", + "coverpkg", + "tags", + "gcflags", + "vet", + "mod", + } + + for _, opt := range stringOpts { + if s, found := t.goOpts[opt].(*string); found && *s != "" { + args = append(args, fmt.Sprintf("-%s=%s", opt, *s)) + } + } + return args +} + +func (t *TestRunner) CompileTo(path string) error { + if t.compiled { + return nil + } + + if t.Suite.Precompiled { + return nil + } + + args := t.BuildArgs(path) + cmd := exec.Command("go", args...) + + output, err := cmd.CombinedOutput() + + if err != nil { + if len(output) > 0 { + return fmt.Errorf("Failed to compile %s:\n\n%s", t.Suite.PackageName, output) + } + return fmt.Errorf("Failed to compile %s", t.Suite.PackageName) + } + + if len(output) > 0 { + fmt.Println(string(output)) + } + + if !fileExists(path) { + compiledFile := t.Suite.PackageName + ".test" + if fileExists(compiledFile) { + // seems like we are on an old go version that does not support the -o flag on go test + // move the compiled test file to the desired location by hand + err = os.Rename(compiledFile, path) + if err != nil { + // We cannot move the file, perhaps because the source and destination + // are on different partitions. We can copy the file, however. + err = copyFile(compiledFile, path) + if err != nil { + return fmt.Errorf("Failed to copy compiled file: %s", err) + } + } + } else { + return fmt.Errorf("Failed to compile %s: output file %q could not be found", t.Suite.PackageName, path) + } + } + + t.compiled = true + + return nil +} + +func fileExists(path string) bool { + _, err := os.Stat(path) + return err == nil || !os.IsNotExist(err) +} + +// copyFile copies the contents of the file named src to the file named +// by dst. The file will be created if it does not already exist. If the +// destination file exists, all it's contents will be replaced by the contents +// of the source file. +func copyFile(src, dst string) error { + srcInfo, err := os.Stat(src) + if err != nil { + return err + } + mode := srcInfo.Mode() + + in, err := os.Open(src) + if err != nil { + return err + } + + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + + defer func() { + closeErr := out.Close() + if err == nil { + err = closeErr + } + }() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + + err = out.Sync() + if err != nil { + return err + } + + return out.Chmod(mode) +} + +func (t *TestRunner) Run() RunResult { + if t.Suite.IsGinkgo { + if t.numCPU > 1 { + if t.parallelStream { + return t.runAndStreamParallelGinkgoSuite() + } else { + return t.runParallelGinkgoSuite() + } + } else { + return t.runSerialGinkgoSuite() + } + } else { + return t.runGoTestSuite() + } +} + +func (t *TestRunner) CleanUp() { + if t.Suite.Precompiled { + return + } + os.Remove(t.compilationTargetPath) +} + +func (t *TestRunner) runSerialGinkgoSuite() RunResult { + ginkgoArgs := config.BuildFlagArgs("ginkgo", config.GinkgoConfig, config.DefaultReporterConfig) + return t.run(t.cmd(ginkgoArgs, os.Stdout, 1), nil) +} + +func (t *TestRunner) runGoTestSuite() RunResult { + return t.run(t.cmd([]string{"-test.v"}, os.Stdout, 1), nil) +} + +func (t *TestRunner) runAndStreamParallelGinkgoSuite() RunResult { + completions := make(chan RunResult) + writers := make([]*logWriter, t.numCPU) + + server, err := remote.NewServer(t.numCPU) + if err != nil { + panic("Failed to start parallel spec server") + } + + server.Start() + defer server.Close() + + for cpu := 0; cpu < t.numCPU; cpu++ { + config.GinkgoConfig.ParallelNode = cpu + 1 + config.GinkgoConfig.ParallelTotal = t.numCPU + config.GinkgoConfig.SyncHost = server.Address() + + ginkgoArgs := config.BuildFlagArgs("ginkgo", config.GinkgoConfig, config.DefaultReporterConfig) + + writers[cpu] = newLogWriter(os.Stdout, cpu+1) + + cmd := t.cmd(ginkgoArgs, writers[cpu], cpu+1) + + server.RegisterAlive(cpu+1, func() bool { + if cmd.ProcessState == nil { + return true + } + return !cmd.ProcessState.Exited() + }) + + go t.run(cmd, completions) + } + + res := PassingRunResult() + + for cpu := 0; cpu < t.numCPU; cpu++ { + res = res.Merge(<-completions) + } + + for _, writer := range writers { + writer.Close() + } + + os.Stdout.Sync() + + if t.shouldCombineCoverprofiles() { + t.combineCoverprofiles() + } + + return res +} + +func (t *TestRunner) runParallelGinkgoSuite() RunResult { + result := make(chan bool) + completions := make(chan RunResult) + writers := make([]*logWriter, t.numCPU) + reports := make([]*bytes.Buffer, t.numCPU) + + stenographer := stenographer.New(!config.DefaultReporterConfig.NoColor, config.GinkgoConfig.FlakeAttempts > 1, colorable.NewColorableStdout()) + aggregator := remote.NewAggregator(t.numCPU, result, config.DefaultReporterConfig, stenographer) + + server, err := remote.NewServer(t.numCPU) + if err != nil { + panic("Failed to start parallel spec server") + } + server.RegisterReporters(aggregator) + server.Start() + defer server.Close() + + for cpu := 0; cpu < t.numCPU; cpu++ { + config.GinkgoConfig.ParallelNode = cpu + 1 + config.GinkgoConfig.ParallelTotal = t.numCPU + config.GinkgoConfig.SyncHost = server.Address() + config.GinkgoConfig.StreamHost = server.Address() + + ginkgoArgs := config.BuildFlagArgs("ginkgo", config.GinkgoConfig, config.DefaultReporterConfig) + + reports[cpu] = &bytes.Buffer{} + writers[cpu] = newLogWriter(reports[cpu], cpu+1) + + cmd := t.cmd(ginkgoArgs, writers[cpu], cpu+1) + + server.RegisterAlive(cpu+1, func() bool { + if cmd.ProcessState == nil { + return true + } + return !cmd.ProcessState.Exited() + }) + + go t.run(cmd, completions) + } + + res := PassingRunResult() + + for cpu := 0; cpu < t.numCPU; cpu++ { + res = res.Merge(<-completions) + } + + //all test processes are done, at this point + //we should be able to wait for the aggregator to tell us that it's done + + select { + case <-result: + fmt.Println("") + case <-time.After(time.Second): + //the aggregator never got back to us! something must have gone wrong + fmt.Println(` + ------------------------------------------------------------------- + | | + | Ginkgo timed out waiting for all parallel nodes to report back! | + | | + -------------------------------------------------------------------`) + fmt.Println("\n", t.Suite.PackageName, "timed out. path:", t.Suite.Path) + os.Stdout.Sync() + + for _, writer := range writers { + writer.Close() + } + + for _, report := range reports { + fmt.Print(report.String()) + } + + os.Stdout.Sync() + } + + if t.shouldCombineCoverprofiles() { + t.combineCoverprofiles() + } + + return res +} + +const CoverProfileSuffix = ".coverprofile" + +func (t *TestRunner) cmd(ginkgoArgs []string, stream io.Writer, node int) *exec.Cmd { + args := []string{"--test.timeout=" + t.timeout.String()} + + coverProfile := t.getCoverProfile() + + if t.shouldCombineCoverprofiles() { + + testCoverProfile := "--test.coverprofile=" + + coverageFile := "" + // Set default name for coverage results + if coverProfile == "" { + coverageFile = t.Suite.PackageName + CoverProfileSuffix + } else { + coverageFile = coverProfile + } + + testCoverProfile += coverageFile + + t.CoverageFile = filepath.Join(t.Suite.Path, coverageFile) + + if t.numCPU > 1 { + testCoverProfile = fmt.Sprintf("%s.%d", testCoverProfile, node) + } + args = append(args, testCoverProfile) + } + + args = append(args, ginkgoArgs...) + args = append(args, t.additionalArgs...) + + path := t.compilationTargetPath + if t.Suite.Precompiled { + path, _ = filepath.Abs(filepath.Join(t.Suite.Path, fmt.Sprintf("%s.test", t.Suite.PackageName))) + } + + cmd := exec.Command(path, args...) + + cmd.Dir = t.Suite.Path + cmd.Stderr = io.MultiWriter(stream, t.stderr) + cmd.Stdout = stream + + return cmd +} + +func (t *TestRunner) shouldCover() bool { + return *t.goOpts["cover"].(*bool) +} + +func (t *TestRunner) shouldRequireSuite() bool { + return *t.goOpts["requireSuite"].(*bool) +} + +func (t *TestRunner) getCoverProfile() string { + return *t.goOpts["coverprofile"].(*string) +} + +func (t *TestRunner) getCoverPackage() string { + return *t.goOpts["coverpkg"].(*string) +} + +func (t *TestRunner) getCoverMode() string { + return *t.goOpts["covermode"].(*string) +} + +func (t *TestRunner) shouldCombineCoverprofiles() bool { + return t.shouldCover() || t.getCoverPackage() != "" || t.getCoverMode() != "" +} + +func (t *TestRunner) run(cmd *exec.Cmd, completions chan RunResult) RunResult { + var res RunResult + + defer func() { + if completions != nil { + completions <- res + } + }() + + err := cmd.Start() + if err != nil { + fmt.Printf("Failed to run test suite!\n\t%s", err.Error()) + return res + } + + cmd.Wait() + + exitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() + res.Passed = (exitStatus == 0) || (exitStatus == types.GINKGO_FOCUS_EXIT_CODE) + res.HasProgrammaticFocus = (exitStatus == types.GINKGO_FOCUS_EXIT_CODE) + + if strings.Contains(t.stderr.String(), "warning: no tests to run") { + if t.shouldRequireSuite() { + res.Passed = false + } + fmt.Fprintf(os.Stderr, `Found no test suites, did you forget to run "ginkgo bootstrap"?`) + } + + return res +} + +func (t *TestRunner) combineCoverprofiles() { + profiles := []string{} + + coverProfile := t.getCoverProfile() + + for cpu := 1; cpu <= t.numCPU; cpu++ { + var coverFile string + if coverProfile == "" { + coverFile = fmt.Sprintf("%s%s.%d", t.Suite.PackageName, CoverProfileSuffix, cpu) + } else { + coverFile = fmt.Sprintf("%s.%d", coverProfile, cpu) + } + + coverFile = filepath.Join(t.Suite.Path, coverFile) + coverProfile, err := ioutil.ReadFile(coverFile) + os.Remove(coverFile) + + if err == nil { + profiles = append(profiles, string(coverProfile)) + } + } + + if len(profiles) != t.numCPU { + return + } + + lines := map[string]int{} + lineOrder := []string{} + for i, coverProfile := range profiles { + for _, line := range strings.Split(coverProfile, "\n")[1:] { + if len(line) == 0 { + continue + } + components := strings.Split(line, " ") + count, _ := strconv.Atoi(components[len(components)-1]) + prefix := strings.Join(components[0:len(components)-1], " ") + lines[prefix] += count + if i == 0 { + lineOrder = append(lineOrder, prefix) + } + } + } + + output := []string{"mode: atomic"} + for _, line := range lineOrder { + output = append(output, fmt.Sprintf("%s %d", line, lines[line])) + } + finalOutput := strings.Join(output, "\n") + + finalFilename := "" + + if coverProfile != "" { + finalFilename = coverProfile + } else { + finalFilename = fmt.Sprintf("%s%s", t.Suite.PackageName, CoverProfileSuffix) + } + + coverageFilepath := filepath.Join(t.Suite.Path, finalFilename) + ioutil.WriteFile(coverageFilepath, []byte(finalOutput), 0666) + + t.CoverageFile = coverageFilepath +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/testsuite/test_suite.go b/vendor/github.com/onsi/ginkgo/ginkgo/testsuite/test_suite.go new file mode 100644 index 000000000..9de8c2bb4 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/testsuite/test_suite.go @@ -0,0 +1,115 @@ +package testsuite + +import ( + "errors" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" +) + +type TestSuite struct { + Path string + PackageName string + IsGinkgo bool + Precompiled bool +} + +func PrecompiledTestSuite(path string) (TestSuite, error) { + info, err := os.Stat(path) + if err != nil { + return TestSuite{}, err + } + + if info.IsDir() { + return TestSuite{}, errors.New("this is a directory, not a file") + } + + if filepath.Ext(path) != ".test" { + return TestSuite{}, errors.New("this is not a .test binary") + } + + if info.Mode()&0111 == 0 { + return TestSuite{}, errors.New("this is not executable") + } + + dir := relPath(filepath.Dir(path)) + packageName := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) + + return TestSuite{ + Path: dir, + PackageName: packageName, + IsGinkgo: true, + Precompiled: true, + }, nil +} + +func SuitesInDir(dir string, recurse bool) []TestSuite { + suites := []TestSuite{} + + if vendorExperimentCheck(dir) { + return suites + } + + files, _ := ioutil.ReadDir(dir) + re := regexp.MustCompile(`^[^._].*_test\.go$`) + for _, file := range files { + if !file.IsDir() && re.Match([]byte(file.Name())) { + suites = append(suites, New(dir, files)) + break + } + } + + if recurse { + re = regexp.MustCompile(`^[._]`) + for _, file := range files { + if file.IsDir() && !re.Match([]byte(file.Name())) { + suites = append(suites, SuitesInDir(dir+"/"+file.Name(), recurse)...) + } + } + } + + return suites +} + +func relPath(dir string) string { + dir, _ = filepath.Abs(dir) + cwd, _ := os.Getwd() + dir, _ = filepath.Rel(cwd, filepath.Clean(dir)) + + if string(dir[0]) != "." { + dir = "." + string(filepath.Separator) + dir + } + + return dir +} + +func New(dir string, files []os.FileInfo) TestSuite { + return TestSuite{ + Path: relPath(dir), + PackageName: packageNameForSuite(dir), + IsGinkgo: filesHaveGinkgoSuite(dir, files), + } +} + +func packageNameForSuite(dir string) string { + path, _ := filepath.Abs(dir) + return filepath.Base(path) +} + +func filesHaveGinkgoSuite(dir string, files []os.FileInfo) bool { + reTestFile := regexp.MustCompile(`_test\.go$`) + reGinkgo := regexp.MustCompile(`package ginkgo|\/ginkgo"`) + + for _, file := range files { + if !file.IsDir() && reTestFile.Match([]byte(file.Name())) { + contents, _ := ioutil.ReadFile(dir + "/" + file.Name()) + if reGinkgo.Match(contents) { + return true + } + } + } + + return false +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/testsuite/vendor_check_go15.go b/vendor/github.com/onsi/ginkgo/ginkgo/testsuite/vendor_check_go15.go new file mode 100644 index 000000000..75f827a12 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/testsuite/vendor_check_go15.go @@ -0,0 +1,16 @@ +// +build !go1.6 + +package testsuite + +import ( + "os" + "path" +) + +// "This change will only be enabled if the go command is run with +// GO15VENDOREXPERIMENT=1 in its environment." +// c.f. the vendor-experiment proposal https://goo.gl/2ucMeC +func vendorExperimentCheck(dir string) bool { + vendorExperiment := os.Getenv("GO15VENDOREXPERIMENT") + return vendorExperiment == "1" && path.Base(dir) == "vendor" +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/testsuite/vendor_check_go16.go b/vendor/github.com/onsi/ginkgo/ginkgo/testsuite/vendor_check_go16.go new file mode 100644 index 000000000..596e5e5c1 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/testsuite/vendor_check_go16.go @@ -0,0 +1,15 @@ +// +build go1.6 + +package testsuite + +import ( + "os" + "path" +) + +// in 1.6 the vendor directory became the default go behaviour, so now +// check if its disabled. +func vendorExperimentCheck(dir string) bool { + vendorExperiment := os.Getenv("GO15VENDOREXPERIMENT") + return vendorExperiment != "0" && path.Base(dir) == "vendor" +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/unfocus_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/unfocus_command.go new file mode 100644 index 000000000..d9dfb6e44 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/unfocus_command.go @@ -0,0 +1,180 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "sync" +) + +func BuildUnfocusCommand() *Command { + return &Command{ + Name: "unfocus", + AltName: "blur", + FlagSet: flag.NewFlagSet("unfocus", flag.ExitOnError), + UsageCommand: "ginkgo unfocus (or ginkgo blur)", + Usage: []string{ + "Recursively unfocuses any focused tests under the current directory", + }, + Command: unfocusSpecs, + } +} + +func unfocusSpecs([]string, []string) { + fmt.Println("Scanning for focus...") + + goFiles := make(chan string) + go func() { + unfocusDir(goFiles, ".") + close(goFiles) + }() + + const workers = 10 + wg := sync.WaitGroup{} + wg.Add(workers) + + for i := 0; i < workers; i++ { + go func() { + for path := range goFiles { + unfocusFile(path) + } + wg.Done() + }() + } + + wg.Wait() +} + +func unfocusDir(goFiles chan string, path string) { + files, err := ioutil.ReadDir(path) + if err != nil { + fmt.Println(err.Error()) + return + } + + for _, f := range files { + switch { + case f.IsDir() && shouldProcessDir(f.Name()): + unfocusDir(goFiles, filepath.Join(path, f.Name())) + case !f.IsDir() && shouldProcessFile(f.Name()): + goFiles <- filepath.Join(path, f.Name()) + } + } +} + +func shouldProcessDir(basename string) bool { + return basename != "vendor" && !strings.HasPrefix(basename, ".") +} + +func shouldProcessFile(basename string) bool { + return strings.HasSuffix(basename, ".go") +} + +func unfocusFile(path string) { + data, err := ioutil.ReadFile(path) + if err != nil { + fmt.Printf("error reading file '%s': %s\n", path, err.Error()) + return + } + + ast, err := parser.ParseFile(token.NewFileSet(), path, bytes.NewReader(data), 0) + if err != nil { + fmt.Printf("error parsing file '%s': %s\n", path, err.Error()) + return + } + + eliminations := scanForFocus(ast) + if len(eliminations) == 0 { + return + } + + fmt.Printf("...updating %s\n", path) + backup, err := writeBackup(path, data) + if err != nil { + fmt.Printf("error creating backup file: %s\n", err.Error()) + return + } + + if err := updateFile(path, data, eliminations); err != nil { + fmt.Printf("error writing file '%s': %s\n", path, err.Error()) + return + } + + os.Remove(backup) +} + +func writeBackup(path string, data []byte) (string, error) { + t, err := ioutil.TempFile(filepath.Dir(path), filepath.Base(path)) + + if err != nil { + return "", fmt.Errorf("error creating temporary file: %w", err) + } + defer t.Close() + + if _, err := io.Copy(t, bytes.NewReader(data)); err != nil { + return "", fmt.Errorf("error writing to temporary file: %w", err) + } + + return t.Name(), nil +} + +func updateFile(path string, data []byte, eliminations []int64) error { + to, err := os.Create(path) + if err != nil { + return fmt.Errorf("error opening file for writing '%s': %w\n", path, err) + } + defer to.Close() + + from := bytes.NewReader(data) + var cursor int64 + for _, byteToEliminate := range eliminations { + if _, err := io.CopyN(to, from, byteToEliminate-cursor); err != nil { + return fmt.Errorf("error copying data: %w", err) + } + + cursor = byteToEliminate + 1 + + if _, err := from.Seek(1, io.SeekCurrent); err != nil { + return fmt.Errorf("error seeking to position in buffer: %w", err) + } + } + + if _, err := io.Copy(to, from); err != nil { + return fmt.Errorf("error copying end data: %w", err) + } + + return nil +} + +func scanForFocus(file *ast.File) (eliminations []int64) { + ast.Inspect(file, func(n ast.Node) bool { + if c, ok := n.(*ast.CallExpr); ok { + if i, ok := c.Fun.(*ast.Ident); ok { + if isFocus(i.Name) { + eliminations = append(eliminations, int64(i.Pos()-file.Pos())) + } + } + } + + return true + }) + + return eliminations +} + +func isFocus(name string) bool { + switch name { + case "FDescribe", "FContext", "FIt", "FMeasure", "FDescribeTable", "FEntry", "FSpecify", "FWhen": + return true + default: + return false + } +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/version_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/version_command.go new file mode 100644 index 000000000..f586908e8 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/version_command.go @@ -0,0 +1,24 @@ +package main + +import ( + "flag" + "fmt" + + "github.com/onsi/ginkgo/config" +) + +func BuildVersionCommand() *Command { + return &Command{ + Name: "version", + FlagSet: flag.NewFlagSet("version", flag.ExitOnError), + UsageCommand: "ginkgo version", + Usage: []string{ + "Print Ginkgo's version", + }, + Command: printVersion, + } +} + +func printVersion([]string, []string) { + fmt.Printf("Ginkgo Version %s\n", config.VERSION) +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/watch/delta.go b/vendor/github.com/onsi/ginkgo/ginkgo/watch/delta.go new file mode 100644 index 000000000..6c485c5b1 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/watch/delta.go @@ -0,0 +1,22 @@ +package watch + +import "sort" + +type Delta struct { + ModifiedPackages []string + + NewSuites []*Suite + RemovedSuites []*Suite + modifiedSuites []*Suite +} + +type DescendingByDelta []*Suite + +func (a DescendingByDelta) Len() int { return len(a) } +func (a DescendingByDelta) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a DescendingByDelta) Less(i, j int) bool { return a[i].Delta() > a[j].Delta() } + +func (d Delta) ModifiedSuites() []*Suite { + sort.Sort(DescendingByDelta(d.modifiedSuites)) + return d.modifiedSuites +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/watch/delta_tracker.go b/vendor/github.com/onsi/ginkgo/ginkgo/watch/delta_tracker.go new file mode 100644 index 000000000..a628303d7 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/watch/delta_tracker.go @@ -0,0 +1,75 @@ +package watch + +import ( + "fmt" + + "regexp" + + "github.com/onsi/ginkgo/ginkgo/testsuite" +) + +type SuiteErrors map[testsuite.TestSuite]error + +type DeltaTracker struct { + maxDepth int + watchRegExp *regexp.Regexp + suites map[string]*Suite + packageHashes *PackageHashes +} + +func NewDeltaTracker(maxDepth int, watchRegExp *regexp.Regexp) *DeltaTracker { + return &DeltaTracker{ + maxDepth: maxDepth, + watchRegExp: watchRegExp, + packageHashes: NewPackageHashes(watchRegExp), + suites: map[string]*Suite{}, + } +} + +func (d *DeltaTracker) Delta(suites []testsuite.TestSuite) (delta Delta, errors SuiteErrors) { + errors = SuiteErrors{} + delta.ModifiedPackages = d.packageHashes.CheckForChanges() + + providedSuitePaths := map[string]bool{} + for _, suite := range suites { + providedSuitePaths[suite.Path] = true + } + + d.packageHashes.StartTrackingUsage() + + for _, suite := range d.suites { + if providedSuitePaths[suite.Suite.Path] { + if suite.Delta() > 0 { + delta.modifiedSuites = append(delta.modifiedSuites, suite) + } + } else { + delta.RemovedSuites = append(delta.RemovedSuites, suite) + } + } + + d.packageHashes.StopTrackingUsageAndPrune() + + for _, suite := range suites { + _, ok := d.suites[suite.Path] + if !ok { + s, err := NewSuite(suite, d.maxDepth, d.packageHashes) + if err != nil { + errors[suite] = err + continue + } + d.suites[suite.Path] = s + delta.NewSuites = append(delta.NewSuites, s) + } + } + + return delta, errors +} + +func (d *DeltaTracker) WillRun(suite testsuite.TestSuite) error { + s, ok := d.suites[suite.Path] + if !ok { + return fmt.Errorf("unknown suite %s", suite.Path) + } + + return s.MarkAsRunAndRecomputedDependencies(d.maxDepth) +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/watch/dependencies.go b/vendor/github.com/onsi/ginkgo/ginkgo/watch/dependencies.go new file mode 100644 index 000000000..f5ddff30f --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/watch/dependencies.go @@ -0,0 +1,92 @@ +package watch + +import ( + "go/build" + "regexp" +) + +var ginkgoAndGomegaFilter = regexp.MustCompile(`github\.com/onsi/ginkgo|github\.com/onsi/gomega`) +var ginkgoIntegrationTestFilter = regexp.MustCompile(`github\.com/onsi/ginkgo/integration`) //allow us to integration test this thing + +type Dependencies struct { + deps map[string]int +} + +func NewDependencies(path string, maxDepth int) (Dependencies, error) { + d := Dependencies{ + deps: map[string]int{}, + } + + if maxDepth == 0 { + return d, nil + } + + err := d.seedWithDepsForPackageAtPath(path) + if err != nil { + return d, err + } + + for depth := 1; depth < maxDepth; depth++ { + n := len(d.deps) + d.addDepsForDepth(depth) + if n == len(d.deps) { + break + } + } + + return d, nil +} + +func (d Dependencies) Dependencies() map[string]int { + return d.deps +} + +func (d Dependencies) seedWithDepsForPackageAtPath(path string) error { + pkg, err := build.ImportDir(path, 0) + if err != nil { + return err + } + + d.resolveAndAdd(pkg.Imports, 1) + d.resolveAndAdd(pkg.TestImports, 1) + d.resolveAndAdd(pkg.XTestImports, 1) + + delete(d.deps, pkg.Dir) + return nil +} + +func (d Dependencies) addDepsForDepth(depth int) { + for dep, depDepth := range d.deps { + if depDepth == depth { + d.addDepsForDep(dep, depth+1) + } + } +} + +func (d Dependencies) addDepsForDep(dep string, depth int) { + pkg, err := build.ImportDir(dep, 0) + if err != nil { + println(err.Error()) + return + } + d.resolveAndAdd(pkg.Imports, depth) +} + +func (d Dependencies) resolveAndAdd(deps []string, depth int) { + for _, dep := range deps { + pkg, err := build.Import(dep, ".", 0) + if err != nil { + continue + } + if !pkg.Goroot && (!ginkgoAndGomegaFilter.Match([]byte(pkg.Dir)) || ginkgoIntegrationTestFilter.Match([]byte(pkg.Dir))) { + d.addDepIfNotPresent(pkg.Dir, depth) + } + } +} + +func (d Dependencies) addDepIfNotPresent(dep string, depth int) { + _, ok := d.deps[dep] + if !ok { + d.deps[dep] = depth + } +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/watch/package_hash.go b/vendor/github.com/onsi/ginkgo/ginkgo/watch/package_hash.go new file mode 100644 index 000000000..67e2c1c32 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/watch/package_hash.go @@ -0,0 +1,104 @@ +package watch + +import ( + "fmt" + "io/ioutil" + "os" + "regexp" + "time" +) + +var goTestRegExp = regexp.MustCompile(`_test\.go$`) + +type PackageHash struct { + CodeModifiedTime time.Time + TestModifiedTime time.Time + Deleted bool + + path string + codeHash string + testHash string + watchRegExp *regexp.Regexp +} + +func NewPackageHash(path string, watchRegExp *regexp.Regexp) *PackageHash { + p := &PackageHash{ + path: path, + watchRegExp: watchRegExp, + } + + p.codeHash, _, p.testHash, _, p.Deleted = p.computeHashes() + + return p +} + +func (p *PackageHash) CheckForChanges() bool { + codeHash, codeModifiedTime, testHash, testModifiedTime, deleted := p.computeHashes() + + if deleted { + if !p.Deleted { + t := time.Now() + p.CodeModifiedTime = t + p.TestModifiedTime = t + } + p.Deleted = true + return true + } + + modified := false + p.Deleted = false + + if p.codeHash != codeHash { + p.CodeModifiedTime = codeModifiedTime + modified = true + } + if p.testHash != testHash { + p.TestModifiedTime = testModifiedTime + modified = true + } + + p.codeHash = codeHash + p.testHash = testHash + return modified +} + +func (p *PackageHash) computeHashes() (codeHash string, codeModifiedTime time.Time, testHash string, testModifiedTime time.Time, deleted bool) { + infos, err := ioutil.ReadDir(p.path) + + if err != nil { + deleted = true + return + } + + for _, info := range infos { + if info.IsDir() { + continue + } + + if goTestRegExp.Match([]byte(info.Name())) { + testHash += p.hashForFileInfo(info) + if info.ModTime().After(testModifiedTime) { + testModifiedTime = info.ModTime() + } + continue + } + + if p.watchRegExp.Match([]byte(info.Name())) { + codeHash += p.hashForFileInfo(info) + if info.ModTime().After(codeModifiedTime) { + codeModifiedTime = info.ModTime() + } + } + } + + testHash += codeHash + if codeModifiedTime.After(testModifiedTime) { + testModifiedTime = codeModifiedTime + } + + return +} + +func (p *PackageHash) hashForFileInfo(info os.FileInfo) string { + return fmt.Sprintf("%s_%d_%d", info.Name(), info.Size(), info.ModTime().UnixNano()) +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/watch/package_hashes.go b/vendor/github.com/onsi/ginkgo/ginkgo/watch/package_hashes.go new file mode 100644 index 000000000..b4892bebf --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/watch/package_hashes.go @@ -0,0 +1,85 @@ +package watch + +import ( + "path/filepath" + "regexp" + "sync" +) + +type PackageHashes struct { + PackageHashes map[string]*PackageHash + usedPaths map[string]bool + watchRegExp *regexp.Regexp + lock *sync.Mutex +} + +func NewPackageHashes(watchRegExp *regexp.Regexp) *PackageHashes { + return &PackageHashes{ + PackageHashes: map[string]*PackageHash{}, + usedPaths: nil, + watchRegExp: watchRegExp, + lock: &sync.Mutex{}, + } +} + +func (p *PackageHashes) CheckForChanges() []string { + p.lock.Lock() + defer p.lock.Unlock() + + modified := []string{} + + for _, packageHash := range p.PackageHashes { + if packageHash.CheckForChanges() { + modified = append(modified, packageHash.path) + } + } + + return modified +} + +func (p *PackageHashes) Add(path string) *PackageHash { + p.lock.Lock() + defer p.lock.Unlock() + + path, _ = filepath.Abs(path) + _, ok := p.PackageHashes[path] + if !ok { + p.PackageHashes[path] = NewPackageHash(path, p.watchRegExp) + } + + if p.usedPaths != nil { + p.usedPaths[path] = true + } + return p.PackageHashes[path] +} + +func (p *PackageHashes) Get(path string) *PackageHash { + p.lock.Lock() + defer p.lock.Unlock() + + path, _ = filepath.Abs(path) + if p.usedPaths != nil { + p.usedPaths[path] = true + } + return p.PackageHashes[path] +} + +func (p *PackageHashes) StartTrackingUsage() { + p.lock.Lock() + defer p.lock.Unlock() + + p.usedPaths = map[string]bool{} +} + +func (p *PackageHashes) StopTrackingUsageAndPrune() { + p.lock.Lock() + defer p.lock.Unlock() + + for path := range p.PackageHashes { + if !p.usedPaths[path] { + delete(p.PackageHashes, path) + } + } + + p.usedPaths = nil +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/watch/suite.go b/vendor/github.com/onsi/ginkgo/ginkgo/watch/suite.go new file mode 100644 index 000000000..5deaba7cb --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/watch/suite.go @@ -0,0 +1,87 @@ +package watch + +import ( + "fmt" + "math" + "time" + + "github.com/onsi/ginkgo/ginkgo/testsuite" +) + +type Suite struct { + Suite testsuite.TestSuite + RunTime time.Time + Dependencies Dependencies + + sharedPackageHashes *PackageHashes +} + +func NewSuite(suite testsuite.TestSuite, maxDepth int, sharedPackageHashes *PackageHashes) (*Suite, error) { + deps, err := NewDependencies(suite.Path, maxDepth) + if err != nil { + return nil, err + } + + sharedPackageHashes.Add(suite.Path) + for dep := range deps.Dependencies() { + sharedPackageHashes.Add(dep) + } + + return &Suite{ + Suite: suite, + Dependencies: deps, + + sharedPackageHashes: sharedPackageHashes, + }, nil +} + +func (s *Suite) Delta() float64 { + delta := s.delta(s.Suite.Path, true, 0) * 1000 + for dep, depth := range s.Dependencies.Dependencies() { + delta += s.delta(dep, false, depth) + } + return delta +} + +func (s *Suite) MarkAsRunAndRecomputedDependencies(maxDepth int) error { + s.RunTime = time.Now() + + deps, err := NewDependencies(s.Suite.Path, maxDepth) + if err != nil { + return err + } + + s.sharedPackageHashes.Add(s.Suite.Path) + for dep := range deps.Dependencies() { + s.sharedPackageHashes.Add(dep) + } + + s.Dependencies = deps + + return nil +} + +func (s *Suite) Description() string { + numDeps := len(s.Dependencies.Dependencies()) + pluralizer := "ies" + if numDeps == 1 { + pluralizer = "y" + } + return fmt.Sprintf("%s [%d dependenc%s]", s.Suite.Path, numDeps, pluralizer) +} + +func (s *Suite) delta(packagePath string, includeTests bool, depth int) float64 { + return math.Max(float64(s.dt(packagePath, includeTests)), 0) / float64(depth+1) +} + +func (s *Suite) dt(packagePath string, includeTests bool) time.Duration { + packageHash := s.sharedPackageHashes.Get(packagePath) + var modifiedTime time.Time + if includeTests { + modifiedTime = packageHash.TestModifiedTime + } else { + modifiedTime = packageHash.CodeModifiedTime + } + + return modifiedTime.Sub(s.RunTime) +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/watch_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/watch_command.go new file mode 100644 index 000000000..a6ef053c8 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/ginkgo/watch_command.go @@ -0,0 +1,175 @@ +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() + } +} -- cgit v1.2.3-54-g00ecf