61
vendor/github.com/onsi/ginkgo/v2/ginkgo/build/build_command.go
generated
vendored
Normal file
61
vendor/github.com/onsi/ginkgo/v2/ginkgo/build/build_command.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/internal"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
func BuildBuildCommand() command.Command {
|
||||
var cliConfig = types.NewDefaultCLIConfig()
|
||||
var goFlagsConfig = types.NewDefaultGoFlagsConfig()
|
||||
|
||||
flags, err := types.BuildBuildCommandFlagSet(&cliConfig, &goFlagsConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return command.Command{
|
||||
Name: "build",
|
||||
Flags: flags,
|
||||
Usage: "ginkgo build <FLAGS> <PACKAGES>",
|
||||
ShortDoc: "Build the passed in <PACKAGES> (or the package in the current directory if left blank).",
|
||||
DocLink: "precompiling-suites",
|
||||
Command: func(args []string, _ []string) {
|
||||
var errors []error
|
||||
cliConfig, goFlagsConfig, errors = types.VetAndInitializeCLIAndGoConfig(cliConfig, goFlagsConfig)
|
||||
command.AbortIfErrors("Ginkgo detected configuration issues:", errors)
|
||||
|
||||
buildSpecs(args, cliConfig, goFlagsConfig)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildSpecs(args []string, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig) {
|
||||
suites := internal.FindSuites(args, cliConfig, false).WithoutState(internal.TestSuiteStateSkippedByFilter)
|
||||
if len(suites) == 0 {
|
||||
command.AbortWith("Found no test suites")
|
||||
}
|
||||
|
||||
opc := internal.NewOrderedParallelCompiler(cliConfig.ComputedNumCompilers())
|
||||
opc.StartCompiling(suites, goFlagsConfig)
|
||||
|
||||
for {
|
||||
suiteIdx, suite := opc.Next()
|
||||
if suiteIdx >= len(suites) {
|
||||
break
|
||||
}
|
||||
suites[suiteIdx] = suite
|
||||
if suite.State.Is(internal.TestSuiteStateFailedToCompile) {
|
||||
fmt.Println(suite.CompilationError.Error())
|
||||
} else {
|
||||
fmt.Printf("Compiled %s.test\n", suite.PackageName)
|
||||
}
|
||||
}
|
||||
|
||||
if suites.CountWithState(internal.TestSuiteStateFailedToCompile) > 0 {
|
||||
command.AbortWith("Failed to compile all tests")
|
||||
}
|
||||
}
|
61
vendor/github.com/onsi/ginkgo/v2/ginkgo/command/abort.go
generated
vendored
Normal file
61
vendor/github.com/onsi/ginkgo/v2/ginkgo/command/abort.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
package command
|
||||
|
||||
import "fmt"
|
||||
|
||||
type AbortDetails struct {
|
||||
ExitCode int
|
||||
Error error
|
||||
EmitUsage bool
|
||||
}
|
||||
|
||||
func Abort(details AbortDetails) {
|
||||
panic(details)
|
||||
}
|
||||
|
||||
func AbortGracefullyWith(format string, args ...interface{}) {
|
||||
Abort(AbortDetails{
|
||||
ExitCode: 0,
|
||||
Error: fmt.Errorf(format, args...),
|
||||
EmitUsage: false,
|
||||
})
|
||||
}
|
||||
|
||||
func AbortWith(format string, args ...interface{}) {
|
||||
Abort(AbortDetails{
|
||||
ExitCode: 1,
|
||||
Error: fmt.Errorf(format, args...),
|
||||
EmitUsage: false,
|
||||
})
|
||||
}
|
||||
|
||||
func AbortWithUsage(format string, args ...interface{}) {
|
||||
Abort(AbortDetails{
|
||||
ExitCode: 1,
|
||||
Error: fmt.Errorf(format, args...),
|
||||
EmitUsage: true,
|
||||
})
|
||||
}
|
||||
|
||||
func AbortIfError(preamble string, err error) {
|
||||
if err != nil {
|
||||
Abort(AbortDetails{
|
||||
ExitCode: 1,
|
||||
Error: fmt.Errorf("%s\n%s", preamble, err.Error()),
|
||||
EmitUsage: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func AbortIfErrors(preamble string, errors []error) {
|
||||
if len(errors) > 0 {
|
||||
out := ""
|
||||
for _, err := range errors {
|
||||
out += err.Error()
|
||||
}
|
||||
Abort(AbortDetails{
|
||||
ExitCode: 1,
|
||||
Error: fmt.Errorf("%s\n%s", preamble, out),
|
||||
EmitUsage: false,
|
||||
})
|
||||
}
|
||||
}
|
50
vendor/github.com/onsi/ginkgo/v2/ginkgo/command/command.go
generated
vendored
Normal file
50
vendor/github.com/onsi/ginkgo/v2/ginkgo/command/command.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/formatter"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
Name string
|
||||
Flags types.GinkgoFlagSet
|
||||
Usage string
|
||||
ShortDoc string
|
||||
Documentation string
|
||||
DocLink string
|
||||
Command func(args []string, additionalArgs []string)
|
||||
}
|
||||
|
||||
func (c Command) Run(args []string, additionalArgs []string) {
|
||||
args, err := c.Flags.Parse(args)
|
||||
if err != nil {
|
||||
AbortWithUsage(err.Error())
|
||||
}
|
||||
|
||||
c.Command(args, additionalArgs)
|
||||
}
|
||||
|
||||
func (c Command) EmitUsage(writer io.Writer) {
|
||||
fmt.Fprintln(writer, formatter.F("{{bold}}"+c.Usage+"{{/}}"))
|
||||
fmt.Fprintln(writer, formatter.F("{{gray}}%s{{/}}", strings.Repeat("-", len(c.Usage))))
|
||||
if c.ShortDoc != "" {
|
||||
fmt.Fprintln(writer, formatter.Fiw(0, formatter.COLS, c.ShortDoc))
|
||||
fmt.Fprintln(writer, "")
|
||||
}
|
||||
if c.Documentation != "" {
|
||||
fmt.Fprintln(writer, formatter.Fiw(0, formatter.COLS, c.Documentation))
|
||||
fmt.Fprintln(writer, "")
|
||||
}
|
||||
if c.DocLink != "" {
|
||||
fmt.Fprintln(writer, formatter.Fi(0, "{{bold}}Learn more at:{{/}} {{cyan}}{{underline}}http://onsi.github.io/ginkgo/#%s{{/}}", c.DocLink))
|
||||
fmt.Fprintln(writer, "")
|
||||
}
|
||||
flagUsage := c.Flags.Usage()
|
||||
if flagUsage != "" {
|
||||
fmt.Fprintf(writer, formatter.F(flagUsage))
|
||||
}
|
||||
}
|
182
vendor/github.com/onsi/ginkgo/v2/ginkgo/command/program.go
generated
vendored
Normal file
182
vendor/github.com/onsi/ginkgo/v2/ginkgo/command/program.go
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/formatter"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
type Program struct {
|
||||
Name string
|
||||
Heading string
|
||||
Commands []Command
|
||||
DefaultCommand Command
|
||||
DeprecatedCommands []DeprecatedCommand
|
||||
|
||||
//For testing - leave as nil in production
|
||||
OutWriter io.Writer
|
||||
ErrWriter io.Writer
|
||||
Exiter func(code int)
|
||||
}
|
||||
|
||||
type DeprecatedCommand struct {
|
||||
Name string
|
||||
Deprecation types.Deprecation
|
||||
}
|
||||
|
||||
func (p Program) RunAndExit(osArgs []string) {
|
||||
var command Command
|
||||
deprecationTracker := types.NewDeprecationTracker()
|
||||
if p.Exiter == nil {
|
||||
p.Exiter = os.Exit
|
||||
}
|
||||
if p.OutWriter == nil {
|
||||
p.OutWriter = formatter.ColorableStdOut
|
||||
}
|
||||
if p.ErrWriter == nil {
|
||||
p.ErrWriter = formatter.ColorableStdErr
|
||||
}
|
||||
|
||||
defer func() {
|
||||
exitCode := 0
|
||||
|
||||
if r := recover(); r != nil {
|
||||
details, ok := r.(AbortDetails)
|
||||
if !ok {
|
||||
panic(r)
|
||||
}
|
||||
|
||||
if details.Error != nil {
|
||||
fmt.Fprintln(p.ErrWriter, formatter.F("{{red}}{{bold}}%s %s{{/}} {{red}}failed{{/}}", p.Name, command.Name))
|
||||
fmt.Fprintln(p.ErrWriter, formatter.Fi(1, details.Error.Error()))
|
||||
}
|
||||
if details.EmitUsage {
|
||||
if details.Error != nil {
|
||||
fmt.Fprintln(p.ErrWriter, "")
|
||||
}
|
||||
command.EmitUsage(p.ErrWriter)
|
||||
}
|
||||
exitCode = details.ExitCode
|
||||
}
|
||||
|
||||
command.Flags.ValidateDeprecations(deprecationTracker)
|
||||
if deprecationTracker.DidTrackDeprecations() {
|
||||
fmt.Fprintln(p.ErrWriter, deprecationTracker.DeprecationsReport())
|
||||
}
|
||||
p.Exiter(exitCode)
|
||||
return
|
||||
}()
|
||||
|
||||
args, additionalArgs := []string{}, []string{}
|
||||
|
||||
foundDelimiter := false
|
||||
for _, arg := range osArgs[1:] {
|
||||
if !foundDelimiter {
|
||||
if arg == "--" {
|
||||
foundDelimiter = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if foundDelimiter {
|
||||
additionalArgs = append(additionalArgs, arg)
|
||||
} else {
|
||||
args = append(args, arg)
|
||||
}
|
||||
}
|
||||
|
||||
command = p.DefaultCommand
|
||||
if len(args) > 0 {
|
||||
p.handleHelpRequestsAndExit(p.OutWriter, args)
|
||||
if command.Name == args[0] {
|
||||
args = args[1:]
|
||||
} else {
|
||||
for _, deprecatedCommand := range p.DeprecatedCommands {
|
||||
if deprecatedCommand.Name == args[0] {
|
||||
deprecationTracker.TrackDeprecation(deprecatedCommand.Deprecation)
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, tryCommand := range p.Commands {
|
||||
if tryCommand.Name == args[0] {
|
||||
command, args = tryCommand, args[1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
command.Run(args, additionalArgs)
|
||||
}
|
||||
|
||||
func (p Program) handleHelpRequestsAndExit(writer io.Writer, args []string) {
|
||||
if len(args) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
matchesHelpFlag := func(args ...string) bool {
|
||||
for _, arg := range args {
|
||||
if arg == "--help" || arg == "-help" || arg == "-h" || arg == "--h" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if len(args) == 1 {
|
||||
if args[0] == "help" || matchesHelpFlag(args[0]) {
|
||||
p.EmitUsage(writer)
|
||||
Abort(AbortDetails{})
|
||||
}
|
||||
} else {
|
||||
var name string
|
||||
if args[0] == "help" || matchesHelpFlag(args[0]) {
|
||||
name = args[1]
|
||||
} else if matchesHelpFlag(args[1:]...) {
|
||||
name = args[0]
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
if p.DefaultCommand.Name == name || p.Name == name {
|
||||
p.DefaultCommand.EmitUsage(writer)
|
||||
Abort(AbortDetails{})
|
||||
}
|
||||
for _, command := range p.Commands {
|
||||
if command.Name == name {
|
||||
command.EmitUsage(writer)
|
||||
Abort(AbortDetails{})
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(writer, formatter.F("{{red}}Unknown Command: {{bold}}%s{{/}}", name))
|
||||
fmt.Fprintln(writer, "")
|
||||
p.EmitUsage(writer)
|
||||
Abort(AbortDetails{ExitCode: 1})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p Program) EmitUsage(writer io.Writer) {
|
||||
fmt.Fprintln(writer, formatter.F(p.Heading))
|
||||
fmt.Fprintln(writer, formatter.F("{{gray}}%s{{/}}", strings.Repeat("-", len(p.Heading))))
|
||||
fmt.Fprintln(writer, formatter.F("For usage information for a command, run {{bold}}%s help COMMAND{{/}}.", p.Name))
|
||||
fmt.Fprintln(writer, formatter.F("For usage information for the default command, run {{bold}}%s help %s{{/}} or {{bold}}%s help %s{{/}}.", p.Name, p.Name, p.Name, p.DefaultCommand.Name))
|
||||
fmt.Fprintln(writer, "")
|
||||
fmt.Fprintln(writer, formatter.F("The following commands are available:"))
|
||||
|
||||
fmt.Fprintln(writer, formatter.Fi(1, "{{bold}}%s{{/}} or %s {{bold}}%s{{/}} - {{gray}}%s{{/}}", p.Name, p.Name, p.DefaultCommand.Name, p.DefaultCommand.Usage))
|
||||
if p.DefaultCommand.ShortDoc != "" {
|
||||
fmt.Fprintln(writer, formatter.Fi(2, p.DefaultCommand.ShortDoc))
|
||||
}
|
||||
|
||||
for _, command := range p.Commands {
|
||||
fmt.Fprintln(writer, formatter.Fi(1, "{{bold}}%s{{/}} - {{gray}}%s{{/}}", command.Name, command.Usage))
|
||||
if command.ShortDoc != "" {
|
||||
fmt.Fprintln(writer, formatter.Fi(2, command.ShortDoc))
|
||||
}
|
||||
}
|
||||
}
|
48
vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/boostrap_templates.go
generated
vendored
Normal file
48
vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/boostrap_templates.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package generators
|
||||
|
||||
var bootstrapText = `package {{.Package}}
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
{{.GinkgoImport}}
|
||||
{{.GomegaImport}}
|
||||
)
|
||||
|
||||
func Test{{.FormattedName}}(t *testing.T) {
|
||||
{{.GomegaPackage}}RegisterFailHandler({{.GinkgoPackage}}Fail)
|
||||
{{.GinkgoPackage}}RunSpecs(t, "{{.FormattedName}} Suite")
|
||||
}
|
||||
`
|
||||
|
||||
var agoutiBootstrapText = `package {{.Package}}
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
{{.GinkgoImport}}
|
||||
{{.GomegaImport}}
|
||||
"github.com/sclevine/agouti"
|
||||
)
|
||||
|
||||
func Test{{.FormattedName}}(t *testing.T) {
|
||||
{{.GomegaPackage}}RegisterFailHandler({{.GinkgoPackage}}Fail)
|
||||
{{.GinkgoPackage}}RunSpecs(t, "{{.FormattedName}} Suite")
|
||||
}
|
||||
|
||||
var agoutiDriver *agouti.WebDriver
|
||||
|
||||
var _ = {{.GinkgoPackage}}BeforeSuite(func() {
|
||||
// Choose a WebDriver:
|
||||
|
||||
agoutiDriver = agouti.PhantomJS()
|
||||
// agoutiDriver = agouti.Selenium()
|
||||
// agoutiDriver = agouti.ChromeDriver()
|
||||
|
||||
{{.GomegaPackage}}Expect(agoutiDriver.Start()).To({{.GomegaPackage}}Succeed())
|
||||
})
|
||||
|
||||
var _ = {{.GinkgoPackage}}AfterSuite(func() {
|
||||
{{.GomegaPackage}}Expect(agoutiDriver.Stop()).To({{.GomegaPackage}}Succeed())
|
||||
})
|
||||
`
|
113
vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/bootstrap_command.go
generated
vendored
Normal file
113
vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/bootstrap_command.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
sprig "github.com/go-task/slim-sprig"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/internal"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
func BuildBootstrapCommand() command.Command {
|
||||
conf := GeneratorsConfig{}
|
||||
flags, err := types.NewGinkgoFlagSet(
|
||||
types.GinkgoFlags{
|
||||
{Name: "agouti", KeyPath: "Agouti",
|
||||
Usage: "If set, bootstrap will generate a bootstrap file for writing Agouti tests"},
|
||||
{Name: "nodot", KeyPath: "NoDot",
|
||||
Usage: "If set, bootstrap will generate a bootstrap test file that does not dot-import ginkgo and gomega"},
|
||||
{Name: "internal", KeyPath: "Internal",
|
||||
Usage: "If set, bootstrap will generate a bootstrap test file that uses the regular package name (i.e. `package X`, not `package X_test`)"},
|
||||
{Name: "template", KeyPath: "CustomTemplate",
|
||||
UsageArgument: "template-file",
|
||||
Usage: "If specified, generate will use the contents of the file passed as the bootstrap template"},
|
||||
},
|
||||
&conf,
|
||||
types.GinkgoFlagSections{},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return command.Command{
|
||||
Name: "bootstrap",
|
||||
Usage: "ginkgo bootstrap",
|
||||
ShortDoc: "Bootstrap a test suite for the current package",
|
||||
Documentation: `Tests written in Ginkgo and Gomega require a small amount of boilerplate to hook into Go's testing infrastructure.
|
||||
|
||||
{{bold}}ginkgo bootstrap{{/}} generates this boilerplate for you in a file named X_suite_test.go where X is the name of the package under test.`,
|
||||
DocLink: "generators",
|
||||
Flags: flags,
|
||||
Command: func(_ []string, _ []string) {
|
||||
generateBootstrap(conf)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type bootstrapData struct {
|
||||
Package string
|
||||
FormattedName string
|
||||
|
||||
GinkgoImport string
|
||||
GomegaImport string
|
||||
GinkgoPackage string
|
||||
GomegaPackage string
|
||||
}
|
||||
|
||||
func generateBootstrap(conf GeneratorsConfig) {
|
||||
packageName, bootstrapFilePrefix, formattedName := getPackageAndFormattedName()
|
||||
|
||||
data := bootstrapData{
|
||||
Package: determinePackageName(packageName, conf.Internal),
|
||||
FormattedName: formattedName,
|
||||
|
||||
GinkgoImport: `. "github.com/onsi/ginkgo/v2"`,
|
||||
GomegaImport: `. "github.com/onsi/gomega"`,
|
||||
GinkgoPackage: "",
|
||||
GomegaPackage: "",
|
||||
}
|
||||
|
||||
if conf.NoDot {
|
||||
data.GinkgoImport = `"github.com/onsi/ginkgo/v2"`
|
||||
data.GomegaImport = `"github.com/onsi/gomega"`
|
||||
data.GinkgoPackage = `ginkgo.`
|
||||
data.GomegaPackage = `gomega.`
|
||||
}
|
||||
|
||||
targetFile := fmt.Sprintf("%s_suite_test.go", bootstrapFilePrefix)
|
||||
if internal.FileExists(targetFile) {
|
||||
command.AbortWith("{{bold}}%s{{/}} already exists", targetFile)
|
||||
} else {
|
||||
fmt.Printf("Generating ginkgo test suite bootstrap for %s in:\n\t%s\n", packageName, targetFile)
|
||||
}
|
||||
|
||||
f, err := os.Create(targetFile)
|
||||
command.AbortIfError("Failed to create file:", err)
|
||||
defer f.Close()
|
||||
|
||||
var templateText string
|
||||
if conf.CustomTemplate != "" {
|
||||
tpl, err := os.ReadFile(conf.CustomTemplate)
|
||||
command.AbortIfError("Failed to read custom bootstrap file:", err)
|
||||
templateText = string(tpl)
|
||||
} else if conf.Agouti {
|
||||
templateText = agoutiBootstrapText
|
||||
} else {
|
||||
templateText = bootstrapText
|
||||
}
|
||||
|
||||
bootstrapTemplate, err := template.New("bootstrap").Funcs(sprig.TxtFuncMap()).Parse(templateText)
|
||||
command.AbortIfError("Failed to parse bootstrap template:", err)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
bootstrapTemplate.Execute(buf, data)
|
||||
|
||||
buf.WriteTo(f)
|
||||
|
||||
internal.GoFmt(targetFile)
|
||||
}
|
239
vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/generate_command.go
generated
vendored
Normal file
239
vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/generate_command.go
generated
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
sprig "github.com/go-task/slim-sprig"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/internal"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
func BuildGenerateCommand() command.Command {
|
||||
conf := GeneratorsConfig{}
|
||||
flags, err := types.NewGinkgoFlagSet(
|
||||
types.GinkgoFlags{
|
||||
{Name: "agouti", KeyPath: "Agouti",
|
||||
Usage: "If set, generate will create a test file for writing Agouti tests"},
|
||||
{Name: "nodot", KeyPath: "NoDot",
|
||||
Usage: "If set, generate will create a test file that does not dot-import ginkgo and gomega"},
|
||||
{Name: "internal", KeyPath: "Internal",
|
||||
Usage: "If set, generate will create a test file that uses the regular package name (i.e. `package X`, not `package X_test`)"},
|
||||
{Name: "template", KeyPath: "CustomTemplate",
|
||||
UsageArgument: "template-file",
|
||||
Usage: "If specified, generate will use the contents of the file passed as the test file template"},
|
||||
},
|
||||
&conf,
|
||||
types.GinkgoFlagSections{},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return command.Command{
|
||||
Name: "generate",
|
||||
Usage: "ginkgo generate <filename(s)>",
|
||||
ShortDoc: "Generate a test file named <filename>_test.go",
|
||||
Documentation: `If the optional <filename> argument is omitted, a file named after the package in the current directory will be created.
|
||||
|
||||
You can pass multiple <filename(s)> to generate multiple files simultaneously. The resulting files are named <filename>_test.go.
|
||||
|
||||
You can also pass a <filename> of the form "file.go" and generate will emit "file_test.go".`,
|
||||
DocLink: "generators",
|
||||
Flags: flags,
|
||||
Command: func(args []string, _ []string) {
|
||||
generateTestFiles(conf, args)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type specData struct {
|
||||
Package string
|
||||
Subject string
|
||||
PackageImportPath string
|
||||
ImportPackage bool
|
||||
|
||||
GinkgoImport string
|
||||
GomegaImport string
|
||||
GinkgoPackage string
|
||||
GomegaPackage string
|
||||
}
|
||||
|
||||
func generateTestFiles(conf GeneratorsConfig, args []string) {
|
||||
subjects := args
|
||||
if len(subjects) == 0 {
|
||||
subjects = []string{""}
|
||||
}
|
||||
for _, subject := range subjects {
|
||||
generateTestFileForSubject(subject, conf)
|
||||
}
|
||||
}
|
||||
|
||||
func generateTestFileForSubject(subject string, conf GeneratorsConfig) {
|
||||
packageName, specFilePrefix, formattedName := getPackageAndFormattedName()
|
||||
if subject != "" {
|
||||
specFilePrefix = formatSubject(subject)
|
||||
formattedName = prettifyName(specFilePrefix)
|
||||
}
|
||||
|
||||
if conf.Internal {
|
||||
specFilePrefix = specFilePrefix + "_internal"
|
||||
}
|
||||
|
||||
data := specData{
|
||||
Package: determinePackageName(packageName, conf.Internal),
|
||||
Subject: formattedName,
|
||||
PackageImportPath: getPackageImportPath(),
|
||||
ImportPackage: !conf.Internal,
|
||||
|
||||
GinkgoImport: `. "github.com/onsi/ginkgo/v2"`,
|
||||
GomegaImport: `. "github.com/onsi/gomega"`,
|
||||
GinkgoPackage: "",
|
||||
GomegaPackage: "",
|
||||
}
|
||||
|
||||
if conf.NoDot {
|
||||
data.GinkgoImport = `"github.com/onsi/ginkgo/v2"`
|
||||
data.GomegaImport = `"github.com/onsi/gomega"`
|
||||
data.GinkgoPackage = `ginkgo.`
|
||||
data.GomegaPackage = `gomega.`
|
||||
}
|
||||
|
||||
targetFile := fmt.Sprintf("%s_test.go", specFilePrefix)
|
||||
if internal.FileExists(targetFile) {
|
||||
command.AbortWith("{{bold}}%s{{/}} already exists", targetFile)
|
||||
} else {
|
||||
fmt.Printf("Generating ginkgo test for %s in:\n %s\n", data.Subject, targetFile)
|
||||
}
|
||||
|
||||
f, err := os.Create(targetFile)
|
||||
command.AbortIfError("Failed to create test file:", err)
|
||||
defer f.Close()
|
||||
|
||||
var templateText string
|
||||
if conf.CustomTemplate != "" {
|
||||
tpl, err := os.ReadFile(conf.CustomTemplate)
|
||||
command.AbortIfError("Failed to read custom template file:", err)
|
||||
templateText = string(tpl)
|
||||
} else if conf.Agouti {
|
||||
templateText = agoutiSpecText
|
||||
} else {
|
||||
templateText = specText
|
||||
}
|
||||
|
||||
specTemplate, err := template.New("spec").Funcs(sprig.TxtFuncMap()).Parse(templateText)
|
||||
command.AbortIfError("Failed to read parse test template:", err)
|
||||
|
||||
specTemplate.Execute(f, data)
|
||||
internal.GoFmt(targetFile)
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
41
vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/generate_templates.go
generated
vendored
Normal file
41
vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/generate_templates.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
package generators
|
||||
|
||||
var specText = `package {{.Package}}
|
||||
|
||||
import (
|
||||
{{.GinkgoImport}}
|
||||
{{.GomegaImport}}
|
||||
|
||||
{{if .ImportPackage}}"{{.PackageImportPath}}"{{end}}
|
||||
)
|
||||
|
||||
var _ = {{.GinkgoPackage}}Describe("{{.Subject}}", func() {
|
||||
|
||||
})
|
||||
`
|
||||
|
||||
var agoutiSpecText = `package {{.Package}}
|
||||
|
||||
import (
|
||||
{{.GinkgoImport}}
|
||||
{{.GomegaImport}}
|
||||
"github.com/sclevine/agouti"
|
||||
. "github.com/sclevine/agouti/matchers"
|
||||
|
||||
{{if .ImportPackage}}"{{.PackageImportPath}}"{{end}}
|
||||
)
|
||||
|
||||
var _ = {{.GinkgoPackage}}Describe("{{.Subject}}", func() {
|
||||
var page *agouti.Page
|
||||
|
||||
{{.GinkgoPackage}}BeforeEach(func() {
|
||||
var err error
|
||||
page, err = agoutiDriver.NewPage()
|
||||
{{.GomegaPackage}}Expect(err).NotTo({{.GomegaPackage}}HaveOccurred())
|
||||
})
|
||||
|
||||
{{.GinkgoPackage}}AfterEach(func() {
|
||||
{{.GomegaPackage}}Expect(page.Destroy()).To({{.GomegaPackage}}Succeed())
|
||||
})
|
||||
})
|
||||
`
|
63
vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/generators_common.go
generated
vendored
Normal file
63
vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/generators_common.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
)
|
||||
|
||||
type GeneratorsConfig struct {
|
||||
Agouti, NoDot, Internal bool
|
||||
CustomTemplate string
|
||||
}
|
||||
|
||||
func getPackageAndFormattedName() (string, string, string) {
|
||||
path, err := os.Getwd()
|
||||
command.AbortIfError("Could not get current working directory:", err)
|
||||
|
||||
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 = ensureLegalPackageName(dirName)
|
||||
}
|
||||
|
||||
formattedName := prettifyName(filepath.Base(path))
|
||||
return packageName, dirName, formattedName
|
||||
}
|
||||
|
||||
func ensureLegalPackageName(name string) string {
|
||||
if name == "_" {
|
||||
return "underscore"
|
||||
}
|
||||
if len(name) == 0 {
|
||||
return "empty"
|
||||
}
|
||||
n, isDigitErr := strconv.Atoi(string(name[0]))
|
||||
if isDigitErr == nil {
|
||||
return []string{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"}[n] + name[1:]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func prettifyName(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"
|
||||
}
|
152
vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/compile.go
generated
vendored
Normal file
152
vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/compile.go
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
func CompileSuite(suite TestSuite, goFlagsConfig types.GoFlagsConfig) TestSuite {
|
||||
if suite.PathToCompiledTest != "" {
|
||||
return suite
|
||||
}
|
||||
|
||||
suite.CompilationError = nil
|
||||
|
||||
path, err := filepath.Abs(filepath.Join(suite.Path, suite.PackageName+".test"))
|
||||
if err != nil {
|
||||
suite.State = TestSuiteStateFailedToCompile
|
||||
suite.CompilationError = fmt.Errorf("Failed to compute compilation target path:\n%s", err.Error())
|
||||
return suite
|
||||
}
|
||||
|
||||
args, err := types.GenerateGoTestCompileArgs(goFlagsConfig, path, "./")
|
||||
if err != nil {
|
||||
suite.State = TestSuiteStateFailedToCompile
|
||||
suite.CompilationError = fmt.Errorf("Failed to generate go test compile flags:\n%s", err.Error())
|
||||
return suite
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", args...)
|
||||
cmd.Dir = suite.Path
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if len(output) > 0 {
|
||||
suite.State = TestSuiteStateFailedToCompile
|
||||
suite.CompilationError = fmt.Errorf("Failed to compile %s:\n\n%s", suite.PackageName, output)
|
||||
} else {
|
||||
suite.State = TestSuiteStateFailedToCompile
|
||||
suite.CompilationError = fmt.Errorf("Failed to compile %s\n%s", suite.PackageName, err.Error())
|
||||
}
|
||||
return suite
|
||||
}
|
||||
|
||||
if strings.Contains(string(output), "[no test files]") {
|
||||
suite.State = TestSuiteStateSkippedDueToEmptyCompilation
|
||||
return suite
|
||||
}
|
||||
|
||||
if len(output) > 0 {
|
||||
fmt.Println(string(output))
|
||||
}
|
||||
|
||||
if !FileExists(path) {
|
||||
suite.State = TestSuiteStateFailedToCompile
|
||||
suite.CompilationError = fmt.Errorf("Failed to compile %s:\nOutput file %s could not be found", suite.PackageName, path)
|
||||
return suite
|
||||
}
|
||||
|
||||
suite.State = TestSuiteStateCompiled
|
||||
suite.PathToCompiledTest = path
|
||||
return suite
|
||||
}
|
||||
|
||||
func Cleanup(goFlagsConfig types.GoFlagsConfig, suites ...TestSuite) {
|
||||
if goFlagsConfig.BinaryMustBePreserved() {
|
||||
return
|
||||
}
|
||||
for _, suite := range suites {
|
||||
if !suite.Precompiled {
|
||||
os.Remove(suite.PathToCompiledTest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type parallelSuiteBundle struct {
|
||||
suite TestSuite
|
||||
compiled chan TestSuite
|
||||
}
|
||||
|
||||
type OrderedParallelCompiler struct {
|
||||
mutex *sync.Mutex
|
||||
stopped bool
|
||||
numCompilers int
|
||||
|
||||
idx int
|
||||
numSuites int
|
||||
completionChannels []chan TestSuite
|
||||
}
|
||||
|
||||
func NewOrderedParallelCompiler(numCompilers int) *OrderedParallelCompiler {
|
||||
return &OrderedParallelCompiler{
|
||||
mutex: &sync.Mutex{},
|
||||
numCompilers: numCompilers,
|
||||
}
|
||||
}
|
||||
|
||||
func (opc *OrderedParallelCompiler) StartCompiling(suites TestSuites, goFlagsConfig types.GoFlagsConfig) {
|
||||
opc.stopped = false
|
||||
opc.idx = 0
|
||||
opc.numSuites = len(suites)
|
||||
opc.completionChannels = make([]chan TestSuite, opc.numSuites)
|
||||
|
||||
toCompile := make(chan parallelSuiteBundle, opc.numCompilers)
|
||||
for compiler := 0; compiler < opc.numCompilers; compiler++ {
|
||||
go func() {
|
||||
for bundle := range toCompile {
|
||||
c, suite := bundle.compiled, bundle.suite
|
||||
opc.mutex.Lock()
|
||||
stopped := opc.stopped
|
||||
opc.mutex.Unlock()
|
||||
if !stopped {
|
||||
suite = CompileSuite(suite, goFlagsConfig)
|
||||
}
|
||||
c <- suite
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for idx, suite := range suites {
|
||||
opc.completionChannels[idx] = make(chan TestSuite, 1)
|
||||
toCompile <- parallelSuiteBundle{suite, opc.completionChannels[idx]}
|
||||
if idx == 0 { //compile first suite serially
|
||||
suite = <-opc.completionChannels[0]
|
||||
opc.completionChannels[0] <- suite
|
||||
}
|
||||
}
|
||||
|
||||
close(toCompile)
|
||||
}
|
||||
|
||||
func (opc *OrderedParallelCompiler) Next() (int, TestSuite) {
|
||||
if opc.idx >= opc.numSuites {
|
||||
return opc.numSuites, TestSuite{}
|
||||
}
|
||||
|
||||
idx := opc.idx
|
||||
suite := <-opc.completionChannels[idx]
|
||||
opc.idx = opc.idx + 1
|
||||
|
||||
return idx, suite
|
||||
}
|
||||
|
||||
func (opc *OrderedParallelCompiler) StopAndDrain() {
|
||||
opc.mutex.Lock()
|
||||
opc.stopped = true
|
||||
opc.mutex.Unlock()
|
||||
}
|
237
vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/profiles_and_reports.go
generated
vendored
Normal file
237
vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/profiles_and_reports.go
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/pprof/profile"
|
||||
"github.com/onsi/ginkgo/v2/reporters"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
func AbsPathForGeneratedAsset(assetName string, suite TestSuite, cliConfig types.CLIConfig, process int) string {
|
||||
suffix := ""
|
||||
if process != 0 {
|
||||
suffix = fmt.Sprintf(".%d", process)
|
||||
}
|
||||
if cliConfig.OutputDir == "" {
|
||||
return filepath.Join(suite.AbsPath(), assetName+suffix)
|
||||
}
|
||||
outputDir, _ := filepath.Abs(cliConfig.OutputDir)
|
||||
return filepath.Join(outputDir, suite.NamespacedName()+"_"+assetName+suffix)
|
||||
}
|
||||
|
||||
func FinalizeProfilesAndReportsForSuites(suites TestSuites, cliConfig types.CLIConfig, suiteConfig types.SuiteConfig, reporterConfig types.ReporterConfig, goFlagsConfig types.GoFlagsConfig) ([]string, error) {
|
||||
messages := []string{}
|
||||
suitesWithProfiles := suites.WithState(TestSuiteStatePassed, TestSuiteStateFailed) //anything else won't have actually run and generated a profile
|
||||
|
||||
// merge cover profiles if need be
|
||||
if goFlagsConfig.Cover && !cliConfig.KeepSeparateCoverprofiles {
|
||||
coverProfiles := []string{}
|
||||
for _, suite := range suitesWithProfiles {
|
||||
if !suite.HasProgrammaticFocus {
|
||||
coverProfiles = append(coverProfiles, AbsPathForGeneratedAsset(goFlagsConfig.CoverProfile, suite, cliConfig, 0))
|
||||
}
|
||||
}
|
||||
|
||||
if len(coverProfiles) > 0 {
|
||||
dst := goFlagsConfig.CoverProfile
|
||||
if cliConfig.OutputDir != "" {
|
||||
dst = filepath.Join(cliConfig.OutputDir, goFlagsConfig.CoverProfile)
|
||||
}
|
||||
err := MergeAndCleanupCoverProfiles(coverProfiles, dst)
|
||||
if err != nil {
|
||||
return messages, err
|
||||
}
|
||||
coverage, err := GetCoverageFromCoverProfile(dst)
|
||||
if err != nil {
|
||||
return messages, err
|
||||
}
|
||||
if coverage == 0 {
|
||||
messages = append(messages, "composite coverage: [no statements]")
|
||||
} else if suitesWithProfiles.AnyHaveProgrammaticFocus() {
|
||||
messages = append(messages, fmt.Sprintf("composite coverage: %.1f%% of statements however some suites did not contribute because they included programatically focused specs", coverage))
|
||||
} else {
|
||||
messages = append(messages, fmt.Sprintf("composite coverage: %.1f%% of statements", coverage))
|
||||
}
|
||||
} else {
|
||||
messages = append(messages, "no composite coverage computed: all suites included programatically focused specs")
|
||||
}
|
||||
}
|
||||
|
||||
// copy binaries if need be
|
||||
for _, suite := range suitesWithProfiles {
|
||||
if goFlagsConfig.BinaryMustBePreserved() && cliConfig.OutputDir != "" {
|
||||
src := suite.PathToCompiledTest
|
||||
dst := filepath.Join(cliConfig.OutputDir, suite.NamespacedName()+".test")
|
||||
if suite.Precompiled {
|
||||
if err := CopyFile(src, dst); err != nil {
|
||||
return messages, err
|
||||
}
|
||||
} else {
|
||||
if err := os.Rename(src, dst); err != nil {
|
||||
return messages, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type reportFormat struct {
|
||||
ReportName string
|
||||
GenerateFunc func(types.Report, string) error
|
||||
MergeFunc func([]string, string) ([]string, error)
|
||||
}
|
||||
reportFormats := []reportFormat{}
|
||||
if reporterConfig.JSONReport != "" {
|
||||
reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.JSONReport, GenerateFunc: reporters.GenerateJSONReport, MergeFunc: reporters.MergeAndCleanupJSONReports})
|
||||
}
|
||||
if reporterConfig.JUnitReport != "" {
|
||||
reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.JUnitReport, GenerateFunc: reporters.GenerateJUnitReport, MergeFunc: reporters.MergeAndCleanupJUnitReports})
|
||||
}
|
||||
if reporterConfig.TeamcityReport != "" {
|
||||
reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.TeamcityReport, GenerateFunc: reporters.GenerateTeamcityReport, MergeFunc: reporters.MergeAndCleanupTeamcityReports})
|
||||
}
|
||||
|
||||
// Generate reports for suites that failed to run
|
||||
reportableSuites := suites.ThatAreGinkgoSuites()
|
||||
for _, suite := range reportableSuites.WithState(TestSuiteStateFailedToCompile, TestSuiteStateFailedDueToTimeout, TestSuiteStateSkippedDueToPriorFailures, TestSuiteStateSkippedDueToEmptyCompilation) {
|
||||
report := types.Report{
|
||||
SuitePath: suite.AbsPath(),
|
||||
SuiteConfig: suiteConfig,
|
||||
SuiteSucceeded: false,
|
||||
}
|
||||
switch suite.State {
|
||||
case TestSuiteStateFailedToCompile:
|
||||
report.SpecialSuiteFailureReasons = append(report.SpecialSuiteFailureReasons, suite.CompilationError.Error())
|
||||
case TestSuiteStateFailedDueToTimeout:
|
||||
report.SpecialSuiteFailureReasons = append(report.SpecialSuiteFailureReasons, TIMEOUT_ELAPSED_FAILURE_REASON)
|
||||
case TestSuiteStateSkippedDueToPriorFailures:
|
||||
report.SpecialSuiteFailureReasons = append(report.SpecialSuiteFailureReasons, PRIOR_FAILURES_FAILURE_REASON)
|
||||
case TestSuiteStateSkippedDueToEmptyCompilation:
|
||||
report.SpecialSuiteFailureReasons = append(report.SpecialSuiteFailureReasons, EMPTY_SKIP_FAILURE_REASON)
|
||||
report.SuiteSucceeded = true
|
||||
}
|
||||
|
||||
for _, format := range reportFormats {
|
||||
format.GenerateFunc(report, AbsPathForGeneratedAsset(format.ReportName, suite, cliConfig, 0))
|
||||
}
|
||||
}
|
||||
|
||||
// Merge reports unless we've been asked to keep them separate
|
||||
if !cliConfig.KeepSeparateReports {
|
||||
for _, format := range reportFormats {
|
||||
reports := []string{}
|
||||
for _, suite := range reportableSuites {
|
||||
reports = append(reports, AbsPathForGeneratedAsset(format.ReportName, suite, cliConfig, 0))
|
||||
}
|
||||
dst := format.ReportName
|
||||
if cliConfig.OutputDir != "" {
|
||||
dst = filepath.Join(cliConfig.OutputDir, format.ReportName)
|
||||
}
|
||||
mergeMessages, err := format.MergeFunc(reports, dst)
|
||||
messages = append(messages, mergeMessages...)
|
||||
if err != nil {
|
||||
return messages, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
//loads each profile, combines them, deletes them, stores them in destination
|
||||
func MergeAndCleanupCoverProfiles(profiles []string, destination string) error {
|
||||
combined := &bytes.Buffer{}
|
||||
modeRegex := regexp.MustCompile(`^mode: .*\n`)
|
||||
for i, profile := range profiles {
|
||||
contents, err := os.ReadFile(profile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to read coverage file %s:\n%s", profile, err.Error())
|
||||
}
|
||||
os.Remove(profile)
|
||||
|
||||
// remove the cover mode line from every file
|
||||
// except the first one
|
||||
if i > 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 {
|
||||
return fmt.Errorf("Unable to append to coverprofile:\n%s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
err := os.WriteFile(destination, combined.Bytes(), 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create combined cover profile:\n%s", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetCoverageFromCoverProfile(profile string) (float64, error) {
|
||||
cmd := exec.Command("go", "tool", "cover", "-func", profile)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Could not process Coverprofile %s: %s", profile, err.Error())
|
||||
}
|
||||
re := regexp.MustCompile(`total:\s*\(statements\)\s*(\d*\.\d*)\%`)
|
||||
matches := re.FindStringSubmatch(string(output))
|
||||
if matches == nil {
|
||||
return 0, fmt.Errorf("Could not parse Coverprofile to compute coverage percentage")
|
||||
}
|
||||
coverageString := matches[1]
|
||||
coverage, err := strconv.ParseFloat(coverageString, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Could not parse Coverprofile to compute coverage percentage: %s", err.Error())
|
||||
}
|
||||
|
||||
return coverage, nil
|
||||
}
|
||||
|
||||
func MergeProfiles(profilePaths []string, destination string) error {
|
||||
profiles := []*profile.Profile{}
|
||||
for _, profilePath := range profilePaths {
|
||||
proFile, err := os.Open(profilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not open profile: %s\n%s", profilePath, err.Error())
|
||||
}
|
||||
prof, err := profile.Parse(proFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not parse profile: %s\n%s", profilePath, err.Error())
|
||||
}
|
||||
profiles = append(profiles, prof)
|
||||
os.Remove(profilePath)
|
||||
}
|
||||
|
||||
mergedProfile, err := profile.Merge(profiles)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not merge profiles:\n%s", err.Error())
|
||||
}
|
||||
|
||||
outFile, err := os.Create(destination)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not create merged profile %s:\n%s", destination, err.Error())
|
||||
}
|
||||
err = mergedProfile.Write(outFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not write merged profile %s:\n%s", destination, err.Error())
|
||||
}
|
||||
err = outFile.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not close merged profile %s:\n%s", destination, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
348
vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/run.go
generated
vendored
Normal file
348
vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/run.go
generated
vendored
Normal file
@@ -0,0 +1,348 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/formatter"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
"github.com/onsi/ginkgo/v2/internal/parallel_support"
|
||||
"github.com/onsi/ginkgo/v2/reporters"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
func RunCompiledSuite(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig types.ReporterConfig, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig, additionalArgs []string) TestSuite {
|
||||
suite.State = TestSuiteStateFailed
|
||||
suite.HasProgrammaticFocus = false
|
||||
|
||||
if suite.PathToCompiledTest == "" {
|
||||
return suite
|
||||
}
|
||||
|
||||
if suite.IsGinkgo && cliConfig.ComputedProcs() > 1 {
|
||||
suite = runParallel(suite, ginkgoConfig, reporterConfig, cliConfig, goFlagsConfig, additionalArgs)
|
||||
} else if suite.IsGinkgo {
|
||||
suite = runSerial(suite, ginkgoConfig, reporterConfig, cliConfig, goFlagsConfig, additionalArgs)
|
||||
} else {
|
||||
suite = runGoTest(suite, cliConfig, goFlagsConfig)
|
||||
}
|
||||
runAfterRunHook(cliConfig.AfterRunHook, reporterConfig.NoColor, suite)
|
||||
return suite
|
||||
}
|
||||
|
||||
func buildAndStartCommand(suite TestSuite, args []string, pipeToStdout bool) (*exec.Cmd, *bytes.Buffer) {
|
||||
buf := &bytes.Buffer{}
|
||||
cmd := exec.Command(suite.PathToCompiledTest, args...)
|
||||
cmd.Dir = suite.Path
|
||||
if pipeToStdout {
|
||||
cmd.Stderr = io.MultiWriter(os.Stdout, buf)
|
||||
cmd.Stdout = os.Stdout
|
||||
} else {
|
||||
cmd.Stderr = buf
|
||||
cmd.Stdout = buf
|
||||
}
|
||||
err := cmd.Start()
|
||||
command.AbortIfError("Failed to start test suite", err)
|
||||
|
||||
return cmd, buf
|
||||
}
|
||||
|
||||
func checkForNoTestsWarning(buf *bytes.Buffer) bool {
|
||||
if strings.Contains(buf.String(), "warning: no tests to run") {
|
||||
fmt.Fprintf(os.Stderr, `Found no test suites, did you forget to run "ginkgo bootstrap"?`)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func runGoTest(suite TestSuite, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig) TestSuite {
|
||||
args, err := types.GenerateGoTestRunArgs(goFlagsConfig)
|
||||
command.AbortIfError("Failed to generate test run arguments", err)
|
||||
cmd, buf := buildAndStartCommand(suite, args, true)
|
||||
|
||||
cmd.Wait()
|
||||
|
||||
exitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
passed := (exitStatus == 0) || (exitStatus == types.GINKGO_FOCUS_EXIT_CODE)
|
||||
passed = !(checkForNoTestsWarning(buf) && cliConfig.RequireSuite) && passed
|
||||
if passed {
|
||||
suite.State = TestSuiteStatePassed
|
||||
} else {
|
||||
suite.State = TestSuiteStateFailed
|
||||
}
|
||||
|
||||
return suite
|
||||
}
|
||||
|
||||
func runSerial(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig types.ReporterConfig, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig, additionalArgs []string) TestSuite {
|
||||
if goFlagsConfig.Cover {
|
||||
goFlagsConfig.CoverProfile = AbsPathForGeneratedAsset(goFlagsConfig.CoverProfile, suite, cliConfig, 0)
|
||||
}
|
||||
if goFlagsConfig.BlockProfile != "" {
|
||||
goFlagsConfig.BlockProfile = AbsPathForGeneratedAsset(goFlagsConfig.BlockProfile, suite, cliConfig, 0)
|
||||
}
|
||||
if goFlagsConfig.CPUProfile != "" {
|
||||
goFlagsConfig.CPUProfile = AbsPathForGeneratedAsset(goFlagsConfig.CPUProfile, suite, cliConfig, 0)
|
||||
}
|
||||
if goFlagsConfig.MemProfile != "" {
|
||||
goFlagsConfig.MemProfile = AbsPathForGeneratedAsset(goFlagsConfig.MemProfile, suite, cliConfig, 0)
|
||||
}
|
||||
if goFlagsConfig.MutexProfile != "" {
|
||||
goFlagsConfig.MutexProfile = AbsPathForGeneratedAsset(goFlagsConfig.MutexProfile, suite, cliConfig, 0)
|
||||
}
|
||||
if reporterConfig.JSONReport != "" {
|
||||
reporterConfig.JSONReport = AbsPathForGeneratedAsset(reporterConfig.JSONReport, suite, cliConfig, 0)
|
||||
}
|
||||
if reporterConfig.JUnitReport != "" {
|
||||
reporterConfig.JUnitReport = AbsPathForGeneratedAsset(reporterConfig.JUnitReport, suite, cliConfig, 0)
|
||||
}
|
||||
if reporterConfig.TeamcityReport != "" {
|
||||
reporterConfig.TeamcityReport = AbsPathForGeneratedAsset(reporterConfig.TeamcityReport, suite, cliConfig, 0)
|
||||
}
|
||||
|
||||
args, err := types.GenerateGinkgoTestRunArgs(ginkgoConfig, reporterConfig, goFlagsConfig)
|
||||
command.AbortIfError("Failed to generate test run arguments", err)
|
||||
args = append([]string{"--test.timeout=0"}, args...)
|
||||
args = append(args, additionalArgs...)
|
||||
|
||||
cmd, buf := buildAndStartCommand(suite, args, true)
|
||||
|
||||
cmd.Wait()
|
||||
|
||||
exitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
suite.HasProgrammaticFocus = (exitStatus == types.GINKGO_FOCUS_EXIT_CODE)
|
||||
passed := (exitStatus == 0) || (exitStatus == types.GINKGO_FOCUS_EXIT_CODE)
|
||||
passed = !(checkForNoTestsWarning(buf) && cliConfig.RequireSuite) && passed
|
||||
if passed {
|
||||
suite.State = TestSuiteStatePassed
|
||||
} else {
|
||||
suite.State = TestSuiteStateFailed
|
||||
}
|
||||
|
||||
if suite.HasProgrammaticFocus {
|
||||
if goFlagsConfig.Cover {
|
||||
fmt.Fprintln(os.Stdout, "coverage: no coverfile was generated because specs are programmatically focused")
|
||||
}
|
||||
if goFlagsConfig.BlockProfile != "" {
|
||||
fmt.Fprintln(os.Stdout, "no block profile was generated because specs are programmatically focused")
|
||||
}
|
||||
if goFlagsConfig.CPUProfile != "" {
|
||||
fmt.Fprintln(os.Stdout, "no cpu profile was generated because specs are programmatically focused")
|
||||
}
|
||||
if goFlagsConfig.MemProfile != "" {
|
||||
fmt.Fprintln(os.Stdout, "no mem profile was generated because specs are programmatically focused")
|
||||
}
|
||||
if goFlagsConfig.MutexProfile != "" {
|
||||
fmt.Fprintln(os.Stdout, "no mutex profile was generated because specs are programmatically focused")
|
||||
}
|
||||
}
|
||||
|
||||
return suite
|
||||
}
|
||||
|
||||
func runParallel(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig types.ReporterConfig, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig, additionalArgs []string) TestSuite {
|
||||
type procResult struct {
|
||||
passed bool
|
||||
hasProgrammaticFocus bool
|
||||
}
|
||||
|
||||
numProcs := cliConfig.ComputedProcs()
|
||||
procOutput := make([]*bytes.Buffer, numProcs)
|
||||
coverProfiles := []string{}
|
||||
|
||||
blockProfiles := []string{}
|
||||
cpuProfiles := []string{}
|
||||
memProfiles := []string{}
|
||||
mutexProfiles := []string{}
|
||||
|
||||
procResults := make(chan procResult)
|
||||
|
||||
server, err := parallel_support.NewServer(numProcs, reporters.NewDefaultReporter(reporterConfig, formatter.ColorableStdOut))
|
||||
command.AbortIfError("Failed to start parallel spec server", err)
|
||||
server.Start()
|
||||
defer server.Close()
|
||||
|
||||
if reporterConfig.JSONReport != "" {
|
||||
reporterConfig.JSONReport = AbsPathForGeneratedAsset(reporterConfig.JSONReport, suite, cliConfig, 0)
|
||||
}
|
||||
if reporterConfig.JUnitReport != "" {
|
||||
reporterConfig.JUnitReport = AbsPathForGeneratedAsset(reporterConfig.JUnitReport, suite, cliConfig, 0)
|
||||
}
|
||||
if reporterConfig.TeamcityReport != "" {
|
||||
reporterConfig.TeamcityReport = AbsPathForGeneratedAsset(reporterConfig.TeamcityReport, suite, cliConfig, 0)
|
||||
}
|
||||
|
||||
for proc := 1; proc <= numProcs; proc++ {
|
||||
procGinkgoConfig := ginkgoConfig
|
||||
procGinkgoConfig.ParallelProcess, procGinkgoConfig.ParallelTotal, procGinkgoConfig.ParallelHost = proc, numProcs, server.Address()
|
||||
|
||||
procGoFlagsConfig := goFlagsConfig
|
||||
if goFlagsConfig.Cover {
|
||||
procGoFlagsConfig.CoverProfile = AbsPathForGeneratedAsset(goFlagsConfig.CoverProfile, suite, cliConfig, proc)
|
||||
coverProfiles = append(coverProfiles, procGoFlagsConfig.CoverProfile)
|
||||
}
|
||||
if goFlagsConfig.BlockProfile != "" {
|
||||
procGoFlagsConfig.BlockProfile = AbsPathForGeneratedAsset(goFlagsConfig.BlockProfile, suite, cliConfig, proc)
|
||||
blockProfiles = append(blockProfiles, procGoFlagsConfig.BlockProfile)
|
||||
}
|
||||
if goFlagsConfig.CPUProfile != "" {
|
||||
procGoFlagsConfig.CPUProfile = AbsPathForGeneratedAsset(goFlagsConfig.CPUProfile, suite, cliConfig, proc)
|
||||
cpuProfiles = append(cpuProfiles, procGoFlagsConfig.CPUProfile)
|
||||
}
|
||||
if goFlagsConfig.MemProfile != "" {
|
||||
procGoFlagsConfig.MemProfile = AbsPathForGeneratedAsset(goFlagsConfig.MemProfile, suite, cliConfig, proc)
|
||||
memProfiles = append(memProfiles, procGoFlagsConfig.MemProfile)
|
||||
}
|
||||
if goFlagsConfig.MutexProfile != "" {
|
||||
procGoFlagsConfig.MutexProfile = AbsPathForGeneratedAsset(goFlagsConfig.MutexProfile, suite, cliConfig, proc)
|
||||
mutexProfiles = append(mutexProfiles, procGoFlagsConfig.MutexProfile)
|
||||
}
|
||||
|
||||
args, err := types.GenerateGinkgoTestRunArgs(procGinkgoConfig, reporterConfig, procGoFlagsConfig)
|
||||
command.AbortIfError("Failed to generate test run arguments", err)
|
||||
args = append([]string{"--test.timeout=0"}, args...)
|
||||
args = append(args, additionalArgs...)
|
||||
|
||||
cmd, buf := buildAndStartCommand(suite, args, false)
|
||||
procOutput[proc-1] = buf
|
||||
server.RegisterAlive(proc, func() bool { return cmd.ProcessState == nil || !cmd.ProcessState.Exited() })
|
||||
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
exitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
procResults <- procResult{
|
||||
passed: (exitStatus == 0) || (exitStatus == types.GINKGO_FOCUS_EXIT_CODE),
|
||||
hasProgrammaticFocus: exitStatus == types.GINKGO_FOCUS_EXIT_CODE,
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
passed := true
|
||||
for proc := 1; proc <= cliConfig.ComputedProcs(); proc++ {
|
||||
result := <-procResults
|
||||
passed = passed && result.passed
|
||||
suite.HasProgrammaticFocus = suite.HasProgrammaticFocus || result.hasProgrammaticFocus
|
||||
}
|
||||
if passed {
|
||||
suite.State = TestSuiteStatePassed
|
||||
} else {
|
||||
suite.State = TestSuiteStateFailed
|
||||
}
|
||||
|
||||
select {
|
||||
case <-server.GetSuiteDone():
|
||||
fmt.Println("")
|
||||
case <-time.After(time.Second):
|
||||
//one of the nodes never finished reporting to the server. Something must have gone wrong.
|
||||
fmt.Fprint(formatter.ColorableStdErr, formatter.F("\n{{bold}}{{red}}Ginkgo timed out waiting for all parallel procs to report back{{/}}\n"))
|
||||
fmt.Fprint(formatter.ColorableStdErr, formatter.F("{{gray}}Test suite:{{/}} %s (%s)\n\n", suite.PackageName, suite.Path))
|
||||
fmt.Fprint(formatter.ColorableStdErr, formatter.Fiw(0, formatter.COLS, "This occurs if a parallel process exits before it reports its results to the Ginkgo CLI. The CLI will now print out all the stdout/stderr output it's collected from the running processes. However you may not see anything useful in these logs because the individual test processes usually intercept output to stdout/stderr in order to capture it in the spec reports.\n\nYou may want to try rerunning your test suite with {{light-gray}}--output-interceptor-mode=none{{/}} to see additional output here and debug your suite.\n"))
|
||||
fmt.Fprintln(formatter.ColorableStdErr, " ")
|
||||
for proc := 1; proc <= cliConfig.ComputedProcs(); proc++ {
|
||||
fmt.Fprintf(formatter.ColorableStdErr, formatter.F("{{bold}}Output from proc %d:{{/}}\n", proc))
|
||||
fmt.Fprintln(os.Stderr, formatter.Fi(1, "%s", procOutput[proc-1].String()))
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "** End **")
|
||||
}
|
||||
|
||||
for proc := 1; proc <= cliConfig.ComputedProcs(); proc++ {
|
||||
output := procOutput[proc-1].String()
|
||||
if proc == 1 && checkForNoTestsWarning(procOutput[0]) && cliConfig.RequireSuite {
|
||||
suite.State = TestSuiteStateFailed
|
||||
}
|
||||
if strings.Contains(output, "deprecated Ginkgo functionality") {
|
||||
fmt.Fprintln(os.Stderr, output)
|
||||
}
|
||||
}
|
||||
|
||||
if len(coverProfiles) > 0 {
|
||||
if suite.HasProgrammaticFocus {
|
||||
fmt.Fprintln(os.Stdout, "coverage: no coverfile was generated because specs are programmatically focused")
|
||||
} else {
|
||||
coverProfile := AbsPathForGeneratedAsset(goFlagsConfig.CoverProfile, suite, cliConfig, 0)
|
||||
err := MergeAndCleanupCoverProfiles(coverProfiles, coverProfile)
|
||||
command.AbortIfError("Failed to combine cover profiles", err)
|
||||
|
||||
coverage, err := GetCoverageFromCoverProfile(coverProfile)
|
||||
command.AbortIfError("Failed to compute coverage", err)
|
||||
if coverage == 0 {
|
||||
fmt.Fprintln(os.Stdout, "coverage: [no statements]")
|
||||
} else {
|
||||
fmt.Fprintf(os.Stdout, "coverage: %.1f%% of statements\n", coverage)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(blockProfiles) > 0 {
|
||||
if suite.HasProgrammaticFocus {
|
||||
fmt.Fprintln(os.Stdout, "no block profile was generated because specs are programmatically focused")
|
||||
} else {
|
||||
blockProfile := AbsPathForGeneratedAsset(goFlagsConfig.BlockProfile, suite, cliConfig, 0)
|
||||
err := MergeProfiles(blockProfiles, blockProfile)
|
||||
command.AbortIfError("Failed to combine blockprofiles", err)
|
||||
}
|
||||
}
|
||||
if len(cpuProfiles) > 0 {
|
||||
if suite.HasProgrammaticFocus {
|
||||
fmt.Fprintln(os.Stdout, "no cpu profile was generated because specs are programmatically focused")
|
||||
} else {
|
||||
cpuProfile := AbsPathForGeneratedAsset(goFlagsConfig.CPUProfile, suite, cliConfig, 0)
|
||||
err := MergeProfiles(cpuProfiles, cpuProfile)
|
||||
command.AbortIfError("Failed to combine cpuprofiles", err)
|
||||
}
|
||||
}
|
||||
if len(memProfiles) > 0 {
|
||||
if suite.HasProgrammaticFocus {
|
||||
fmt.Fprintln(os.Stdout, "no mem profile was generated because specs are programmatically focused")
|
||||
} else {
|
||||
memProfile := AbsPathForGeneratedAsset(goFlagsConfig.MemProfile, suite, cliConfig, 0)
|
||||
err := MergeProfiles(memProfiles, memProfile)
|
||||
command.AbortIfError("Failed to combine memprofiles", err)
|
||||
}
|
||||
}
|
||||
if len(mutexProfiles) > 0 {
|
||||
if suite.HasProgrammaticFocus {
|
||||
fmt.Fprintln(os.Stdout, "no mutex profile was generated because specs are programmatically focused")
|
||||
} else {
|
||||
mutexProfile := AbsPathForGeneratedAsset(goFlagsConfig.MutexProfile, suite, cliConfig, 0)
|
||||
err := MergeProfiles(mutexProfiles, mutexProfile)
|
||||
command.AbortIfError("Failed to combine mutexprofiles", err)
|
||||
}
|
||||
}
|
||||
|
||||
return suite
|
||||
}
|
||||
|
||||
func runAfterRunHook(command string, noColor bool, suite TestSuite) {
|
||||
if command == "" {
|
||||
return
|
||||
}
|
||||
f := formatter.NewWithNoColorBool(noColor)
|
||||
|
||||
// Allow for string replacement to pass input to the command
|
||||
passed := "[FAIL]"
|
||||
if suite.State.Is(TestSuiteStatePassed) {
|
||||
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.Fprintln(formatter.ColorableStdOut, f.Fi(0, "{{red}}{{bold}}After-run-hook failed:{{/}}"))
|
||||
fmt.Fprintln(formatter.ColorableStdOut, f.Fi(1, "{{red}}%s{{/}}", output))
|
||||
} else {
|
||||
fmt.Fprintln(formatter.ColorableStdOut, f.Fi(0, "{{green}}{{bold}}After-run-hook succeeded:{{/}}"))
|
||||
fmt.Fprintln(formatter.ColorableStdOut, f.Fi(1, "{{green}}%s{{/}}", output))
|
||||
}
|
||||
}
|
283
vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/test_suite.go
generated
vendored
Normal file
283
vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/test_suite.go
generated
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
const TIMEOUT_ELAPSED_FAILURE_REASON = "Suite did not run because the timeout elapsed"
|
||||
const PRIOR_FAILURES_FAILURE_REASON = "Suite did not run because prior suites failed and --keep-going is not set"
|
||||
const EMPTY_SKIP_FAILURE_REASON = "Suite did not run go test reported that no test files were found"
|
||||
|
||||
type TestSuiteState uint
|
||||
|
||||
const (
|
||||
TestSuiteStateInvalid TestSuiteState = iota
|
||||
|
||||
TestSuiteStateUncompiled
|
||||
TestSuiteStateCompiled
|
||||
|
||||
TestSuiteStatePassed
|
||||
|
||||
TestSuiteStateSkippedDueToEmptyCompilation
|
||||
TestSuiteStateSkippedByFilter
|
||||
TestSuiteStateSkippedDueToPriorFailures
|
||||
|
||||
TestSuiteStateFailed
|
||||
TestSuiteStateFailedDueToTimeout
|
||||
TestSuiteStateFailedToCompile
|
||||
)
|
||||
|
||||
var TestSuiteStateFailureStates = []TestSuiteState{TestSuiteStateFailed, TestSuiteStateFailedDueToTimeout, TestSuiteStateFailedToCompile}
|
||||
|
||||
func (state TestSuiteState) Is(states ...TestSuiteState) bool {
|
||||
for _, suiteState := range states {
|
||||
if suiteState == state {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type TestSuite struct {
|
||||
Path string
|
||||
PackageName string
|
||||
IsGinkgo bool
|
||||
|
||||
Precompiled bool
|
||||
PathToCompiledTest string
|
||||
CompilationError error
|
||||
|
||||
HasProgrammaticFocus bool
|
||||
State TestSuiteState
|
||||
}
|
||||
|
||||
func (ts TestSuite) AbsPath() string {
|
||||
path, _ := filepath.Abs(ts.Path)
|
||||
return path
|
||||
}
|
||||
|
||||
func (ts TestSuite) NamespacedName() string {
|
||||
name := relPath(ts.Path)
|
||||
name = strings.TrimLeft(name, "."+string(filepath.Separator))
|
||||
name = strings.ReplaceAll(name, string(filepath.Separator), "_")
|
||||
name = strings.ReplaceAll(name, " ", "_")
|
||||
if name == "" {
|
||||
return ts.PackageName
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
type TestSuites []TestSuite
|
||||
|
||||
func (ts TestSuites) AnyHaveProgrammaticFocus() bool {
|
||||
for _, suite := range ts {
|
||||
if suite.HasProgrammaticFocus {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (ts TestSuites) ThatAreGinkgoSuites() TestSuites {
|
||||
out := TestSuites{}
|
||||
for _, suite := range ts {
|
||||
if suite.IsGinkgo {
|
||||
out = append(out, suite)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (ts TestSuites) CountWithState(states ...TestSuiteState) int {
|
||||
n := 0
|
||||
for _, suite := range ts {
|
||||
if suite.State.Is(states...) {
|
||||
n += 1
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (ts TestSuites) WithState(states ...TestSuiteState) TestSuites {
|
||||
out := TestSuites{}
|
||||
for _, suite := range ts {
|
||||
if suite.State.Is(states...) {
|
||||
out = append(out, suite)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (ts TestSuites) WithoutState(states ...TestSuiteState) TestSuites {
|
||||
out := TestSuites{}
|
||||
for _, suite := range ts {
|
||||
if !suite.State.Is(states...) {
|
||||
out = append(out, suite)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (ts TestSuites) ShuffledCopy(seed int64) TestSuites {
|
||||
out := make(TestSuites, len(ts))
|
||||
permutation := rand.New(rand.NewSource(seed)).Perm(len(ts))
|
||||
for i, j := range permutation {
|
||||
out[i] = ts[j]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func FindSuites(args []string, cliConfig types.CLIConfig, allowPrecompiled bool) TestSuites {
|
||||
suites := TestSuites{}
|
||||
|
||||
if len(args) > 0 {
|
||||
for _, arg := range args {
|
||||
if allowPrecompiled {
|
||||
suite, err := precompiledTestSuite(arg)
|
||||
if err == nil {
|
||||
suites = append(suites, suite)
|
||||
continue
|
||||
}
|
||||
}
|
||||
recurseForSuite := cliConfig.Recurse
|
||||
if strings.HasSuffix(arg, "/...") && arg != "/..." {
|
||||
arg = arg[:len(arg)-4]
|
||||
recurseForSuite = true
|
||||
}
|
||||
suites = append(suites, suitesInDir(arg, recurseForSuite)...)
|
||||
}
|
||||
} else {
|
||||
suites = suitesInDir(".", cliConfig.Recurse)
|
||||
}
|
||||
|
||||
if cliConfig.SkipPackage != "" {
|
||||
skipFilters := strings.Split(cliConfig.SkipPackage, ",")
|
||||
for idx := range suites {
|
||||
for _, skipFilter := range skipFilters {
|
||||
if strings.Contains(suites[idx].Path, skipFilter) {
|
||||
suites[idx].State = TestSuiteStateSkippedByFilter
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return suites
|
||||
}
|
||||
|
||||
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" && filepath.Ext(path) != ".exe" {
|
||||
return TestSuite{}, errors.New("this is not a .test binary")
|
||||
}
|
||||
|
||||
if filepath.Ext(path) == ".test" && info.Mode()&0111 == 0 {
|
||||
return TestSuite{}, errors.New("this is not executable")
|
||||
}
|
||||
|
||||
dir := relPath(filepath.Dir(path))
|
||||
packageName := strings.TrimSuffix(filepath.Base(path), ".exe")
|
||||
packageName = strings.TrimSuffix(packageName, ".test")
|
||||
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
return TestSuite{}, err
|
||||
}
|
||||
|
||||
return TestSuite{
|
||||
Path: dir,
|
||||
PackageName: packageName,
|
||||
IsGinkgo: true,
|
||||
Precompiled: true,
|
||||
PathToCompiledTest: path,
|
||||
State: TestSuiteStateCompiled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func suitesInDir(dir string, recurse bool) TestSuites {
|
||||
suites := TestSuites{}
|
||||
|
||||
if path.Base(dir) == "vendor" {
|
||||
return suites
|
||||
}
|
||||
|
||||
files, _ := os.ReadDir(dir)
|
||||
re := regexp.MustCompile(`^[^._].*_test\.go$`)
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && re.Match([]byte(file.Name())) {
|
||||
suite := TestSuite{
|
||||
Path: relPath(dir),
|
||||
PackageName: packageNameForSuite(dir),
|
||||
IsGinkgo: filesHaveGinkgoSuite(dir, files),
|
||||
State: TestSuiteStateUncompiled,
|
||||
}
|
||||
suites = append(suites, suite)
|
||||
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 packageNameForSuite(dir string) string {
|
||||
path, _ := filepath.Abs(dir)
|
||||
return filepath.Base(path)
|
||||
}
|
||||
|
||||
func filesHaveGinkgoSuite(dir string, files []os.DirEntry) bool {
|
||||
reTestFile := regexp.MustCompile(`_test\.go$`)
|
||||
reGinkgo := regexp.MustCompile(`package ginkgo|\/ginkgo"|\/ginkgo\/v2"|\/ginkgo\/v2/dsl/`)
|
||||
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && reTestFile.Match([]byte(file.Name())) {
|
||||
contents, _ := os.ReadFile(dir + "/" + file.Name())
|
||||
if reGinkgo.Match(contents) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
86
vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/utils.go
generated
vendored
Normal file
86
vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/utils.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/formatter"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
)
|
||||
|
||||
func FileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func CopyFile(src string, dest string) error {
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srcStat, err := srcFile.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(dest); err == nil {
|
||||
os.Remove(dest)
|
||||
}
|
||||
|
||||
destFile, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE, srcStat.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(destFile, srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := srcFile.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return destFile.Close()
|
||||
}
|
||||
|
||||
func GoFmt(path string) {
|
||||
out, err := exec.Command("go", "fmt", path).CombinedOutput()
|
||||
if err != nil {
|
||||
command.AbortIfError(fmt.Sprintf("Could not fmt:\n%s\n", string(out)), err)
|
||||
}
|
||||
}
|
||||
|
||||
func PluralizedWord(singular, plural string, count int) string {
|
||||
if count == 1 {
|
||||
return singular
|
||||
}
|
||||
return plural
|
||||
}
|
||||
|
||||
func FailedSuitesReport(suites TestSuites, f formatter.Formatter) string {
|
||||
out := ""
|
||||
out += "There were failures detected in the following suites:\n"
|
||||
|
||||
maxPackageNameLength := 0
|
||||
for _, suite := range suites.WithState(TestSuiteStateFailureStates...) {
|
||||
if len(suite.PackageName) > maxPackageNameLength {
|
||||
maxPackageNameLength = len(suite.PackageName)
|
||||
}
|
||||
}
|
||||
|
||||
packageNameFormatter := fmt.Sprintf("%%%ds", maxPackageNameLength)
|
||||
for _, suite := range suites {
|
||||
switch suite.State {
|
||||
case TestSuiteStateFailed:
|
||||
out += f.Fi(1, "{{red}}"+packageNameFormatter+" {{gray}}%s{{/}}\n", suite.PackageName, suite.Path)
|
||||
case TestSuiteStateFailedToCompile:
|
||||
out += f.Fi(1, "{{red}}"+packageNameFormatter+" {{gray}}%s {{magenta}}[Compilation failure]{{/}}\n", suite.PackageName, suite.Path)
|
||||
case TestSuiteStateFailedDueToTimeout:
|
||||
out += f.Fi(1, "{{red}}"+packageNameFormatter+" {{gray}}%s {{orange}}[%s]{{/}}\n", suite.PackageName, suite.Path, TIMEOUT_ELAPSED_FAILURE_REASON)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
123
vendor/github.com/onsi/ginkgo/v2/ginkgo/labels/labels_command.go
generated
vendored
Normal file
123
vendor/github.com/onsi/ginkgo/v2/ginkgo/labels/labels_command.go
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
package labels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/internal"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
func BuildLabelsCommand() command.Command {
|
||||
var cliConfig = types.NewDefaultCLIConfig()
|
||||
|
||||
flags, err := types.BuildLabelsCommandFlagSet(&cliConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return command.Command{
|
||||
Name: "labels",
|
||||
Usage: "ginkgo labels <FLAGS> <PACKAGES>",
|
||||
Flags: flags,
|
||||
ShortDoc: "List labels detected in the passed-in packages (or the package in the current directory if left blank).",
|
||||
DocLink: "spec-labels",
|
||||
Command: func(args []string, _ []string) {
|
||||
ListLabels(args, cliConfig)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ListLabels(args []string, cliConfig types.CLIConfig) {
|
||||
suites := internal.FindSuites(args, cliConfig, false).WithoutState(internal.TestSuiteStateSkippedByFilter)
|
||||
if len(suites) == 0 {
|
||||
command.AbortWith("Found no test suites")
|
||||
}
|
||||
for _, suite := range suites {
|
||||
labels := fetchLabelsFromPackage(suite.Path)
|
||||
if len(labels) == 0 {
|
||||
fmt.Printf("%s: No labels found\n", suite.PackageName)
|
||||
} else {
|
||||
fmt.Printf("%s: [%s]\n", suite.PackageName, strings.Join(labels, ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchLabelsFromPackage(packagePath string) []string {
|
||||
fset := token.NewFileSet()
|
||||
parsedPackages, err := parser.ParseDir(fset, packagePath, nil, 0)
|
||||
command.AbortIfError("Failed to parse package source:", err)
|
||||
|
||||
files := []*ast.File{}
|
||||
hasTestPackage := false
|
||||
for key, pkg := range parsedPackages {
|
||||
if strings.HasSuffix(key, "_test") {
|
||||
hasTestPackage = true
|
||||
for _, file := range pkg.Files {
|
||||
files = append(files, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasTestPackage {
|
||||
for _, pkg := range parsedPackages {
|
||||
for _, file := range pkg.Files {
|
||||
files = append(files, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seen := map[string]bool{}
|
||||
labels := []string{}
|
||||
ispr := inspector.New(files)
|
||||
ispr.Preorder([]ast.Node{&ast.CallExpr{}}, func(n ast.Node) {
|
||||
potentialLabels := fetchLabels(n.(*ast.CallExpr))
|
||||
for _, label := range potentialLabels {
|
||||
if !seen[label] {
|
||||
seen[label] = true
|
||||
labels = append(labels, strconv.Quote(label))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
sort.Strings(labels)
|
||||
return labels
|
||||
}
|
||||
|
||||
func fetchLabels(callExpr *ast.CallExpr) []string {
|
||||
out := []string{}
|
||||
switch expr := callExpr.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
if expr.Name != "Label" {
|
||||
return out
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
if expr.Sel.Name != "Label" {
|
||||
return out
|
||||
}
|
||||
default:
|
||||
return out
|
||||
}
|
||||
for _, arg := range callExpr.Args {
|
||||
switch expr := arg.(type) {
|
||||
case *ast.BasicLit:
|
||||
if expr.Kind == token.STRING {
|
||||
unquoted, err := strconv.Unquote(expr.Value)
|
||||
if err != nil {
|
||||
unquoted = expr.Value
|
||||
}
|
||||
validated, err := types.ValidateAndCleanupLabel(unquoted, types.CodeLocation{})
|
||||
if err == nil {
|
||||
out = append(out, validated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
58
vendor/github.com/onsi/ginkgo/v2/ginkgo/main.go
generated
vendored
Normal file
58
vendor/github.com/onsi/ginkgo/v2/ginkgo/main.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/build"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/generators"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/labels"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/outline"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/run"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/unfocus"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/watch"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
var program command.Program
|
||||
|
||||
func GenerateCommands() []command.Command {
|
||||
return []command.Command{
|
||||
watch.BuildWatchCommand(),
|
||||
build.BuildBuildCommand(),
|
||||
generators.BuildBootstrapCommand(),
|
||||
generators.BuildGenerateCommand(),
|
||||
labels.BuildLabelsCommand(),
|
||||
outline.BuildOutlineCommand(),
|
||||
unfocus.BuildUnfocusCommand(),
|
||||
BuildVersionCommand(),
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
program = command.Program{
|
||||
Name: "ginkgo",
|
||||
Heading: fmt.Sprintf("Ginkgo Version %s", types.VERSION),
|
||||
Commands: GenerateCommands(),
|
||||
DefaultCommand: run.BuildRunCommand(),
|
||||
DeprecatedCommands: []command.DeprecatedCommand{
|
||||
{Name: "convert", Deprecation: types.Deprecations.Convert()},
|
||||
{Name: "blur", Deprecation: types.Deprecations.Blur()},
|
||||
{Name: "nodot", Deprecation: types.Deprecations.Nodot()},
|
||||
},
|
||||
}
|
||||
|
||||
program.RunAndExit(os.Args)
|
||||
}
|
||||
|
||||
func BuildVersionCommand() command.Command {
|
||||
return command.Command{
|
||||
Name: "version",
|
||||
Usage: "ginkgo version",
|
||||
ShortDoc: "Print Ginkgo's version",
|
||||
Command: func(_ []string, _ []string) {
|
||||
fmt.Printf("Ginkgo Version %s\n", types.VERSION)
|
||||
},
|
||||
}
|
||||
}
|
218
vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/ginkgo.go
generated
vendored
Normal file
218
vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/ginkgo.go
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
package outline
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// undefinedTextAlt is used if the spec/container text cannot be derived
|
||||
undefinedTextAlt = "undefined"
|
||||
)
|
||||
|
||||
// ginkgoMetadata holds useful bits of information for every entry in the outline
|
||||
type ginkgoMetadata struct {
|
||||
// Name is the spec or container function name, e.g. `Describe` or `It`
|
||||
Name string `json:"name"`
|
||||
|
||||
// Text is the `text` argument passed to specs, and some containers
|
||||
Text string `json:"text"`
|
||||
|
||||
// Start is the position of first character of the spec or container block
|
||||
Start int `json:"start"`
|
||||
|
||||
// End is the position of first character immediately after the spec or container block
|
||||
End int `json:"end"`
|
||||
|
||||
Spec bool `json:"spec"`
|
||||
Focused bool `json:"focused"`
|
||||
Pending bool `json:"pending"`
|
||||
}
|
||||
|
||||
// ginkgoNode is used to construct the outline as a tree
|
||||
type ginkgoNode struct {
|
||||
ginkgoMetadata
|
||||
Nodes []*ginkgoNode `json:"nodes"`
|
||||
}
|
||||
|
||||
type walkFunc func(n *ginkgoNode)
|
||||
|
||||
func (n *ginkgoNode) PreOrder(f walkFunc) {
|
||||
f(n)
|
||||
for _, m := range n.Nodes {
|
||||
m.PreOrder(f)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *ginkgoNode) PostOrder(f walkFunc) {
|
||||
for _, m := range n.Nodes {
|
||||
m.PostOrder(f)
|
||||
}
|
||||
f(n)
|
||||
}
|
||||
|
||||
func (n *ginkgoNode) Walk(pre, post walkFunc) {
|
||||
pre(n)
|
||||
for _, m := range n.Nodes {
|
||||
m.Walk(pre, post)
|
||||
}
|
||||
post(n)
|
||||
}
|
||||
|
||||
// PropagateInheritedProperties propagates the Pending and Focused properties
|
||||
// through the subtree rooted at n.
|
||||
func (n *ginkgoNode) PropagateInheritedProperties() {
|
||||
n.PreOrder(func(thisNode *ginkgoNode) {
|
||||
for _, descendantNode := range thisNode.Nodes {
|
||||
if thisNode.Pending {
|
||||
descendantNode.Pending = true
|
||||
descendantNode.Focused = false
|
||||
}
|
||||
if thisNode.Focused && !descendantNode.Pending {
|
||||
descendantNode.Focused = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BackpropagateUnfocus propagates the Focused property through the subtree
|
||||
// rooted at n. It applies the rule described in the Ginkgo docs:
|
||||
// > Nested programmatically focused specs follow a simple rule: if a
|
||||
// > leaf-node is marked focused, any of its ancestor nodes that are marked
|
||||
// > focus will be unfocused.
|
||||
func (n *ginkgoNode) BackpropagateUnfocus() {
|
||||
focusedSpecInSubtreeStack := []bool{}
|
||||
n.PostOrder(func(thisNode *ginkgoNode) {
|
||||
if thisNode.Spec {
|
||||
focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, thisNode.Focused)
|
||||
return
|
||||
}
|
||||
focusedSpecInSubtree := false
|
||||
for range thisNode.Nodes {
|
||||
focusedSpecInSubtree = focusedSpecInSubtree || focusedSpecInSubtreeStack[len(focusedSpecInSubtreeStack)-1]
|
||||
focusedSpecInSubtreeStack = focusedSpecInSubtreeStack[0 : len(focusedSpecInSubtreeStack)-1]
|
||||
}
|
||||
focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, focusedSpecInSubtree)
|
||||
if focusedSpecInSubtree {
|
||||
thisNode.Focused = false
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func packageAndIdentNamesFromCallExpr(ce *ast.CallExpr) (string, string, bool) {
|
||||
switch ex := ce.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
return "", ex.Name, true
|
||||
case *ast.SelectorExpr:
|
||||
pkgID, ok := ex.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return "", "", false
|
||||
}
|
||||
// A package identifier is top-level, so Obj must be nil
|
||||
if pkgID.Obj != nil {
|
||||
return "", "", false
|
||||
}
|
||||
if ex.Sel == nil {
|
||||
return "", "", false
|
||||
}
|
||||
return pkgID.Name, ex.Sel.Name, true
|
||||
default:
|
||||
return "", "", false
|
||||
}
|
||||
}
|
||||
|
||||
// absoluteOffsetsForNode derives the absolute character offsets of the node start and
|
||||
// end positions.
|
||||
func absoluteOffsetsForNode(fset *token.FileSet, n ast.Node) (start, end int) {
|
||||
return fset.PositionFor(n.Pos(), false).Offset, fset.PositionFor(n.End(), false).Offset
|
||||
}
|
||||
|
||||
// ginkgoNodeFromCallExpr derives an outline entry from a go AST subtree
|
||||
// corresponding to a Ginkgo container or spec.
|
||||
func ginkgoNodeFromCallExpr(fset *token.FileSet, ce *ast.CallExpr, ginkgoPackageName *string) (*ginkgoNode, bool) {
|
||||
packageName, identName, ok := packageAndIdentNamesFromCallExpr(ce)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
n := ginkgoNode{}
|
||||
n.Name = identName
|
||||
n.Start, n.End = absoluteOffsetsForNode(fset, ce)
|
||||
n.Nodes = make([]*ginkgoNode, 0)
|
||||
switch identName {
|
||||
case "It", "Specify", "Entry":
|
||||
n.Spec = true
|
||||
n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt)
|
||||
return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName
|
||||
case "FIt", "FSpecify", "FEntry":
|
||||
n.Spec = true
|
||||
n.Focused = true
|
||||
n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt)
|
||||
return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName
|
||||
case "PIt", "PSpecify", "XIt", "XSpecify", "PEntry", "XEntry":
|
||||
n.Spec = true
|
||||
n.Pending = true
|
||||
n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt)
|
||||
return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName
|
||||
case "Context", "Describe", "When", "DescribeTable":
|
||||
n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt)
|
||||
return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName
|
||||
case "FContext", "FDescribe", "FWhen", "FDescribeTable":
|
||||
n.Focused = true
|
||||
n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt)
|
||||
return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName
|
||||
case "PContext", "PDescribe", "PWhen", "XContext", "XDescribe", "XWhen", "PDescribeTable", "XDescribeTable":
|
||||
n.Pending = true
|
||||
n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt)
|
||||
return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName
|
||||
case "By":
|
||||
n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt)
|
||||
return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName
|
||||
case "AfterEach", "BeforeEach":
|
||||
return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName
|
||||
case "JustAfterEach", "JustBeforeEach":
|
||||
return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName
|
||||
case "AfterSuite", "BeforeSuite":
|
||||
return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName
|
||||
case "SynchronizedAfterSuite", "SynchronizedBeforeSuite":
|
||||
return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// textOrAltFromCallExpr tries to derive the "text" of a Ginkgo spec or
|
||||
// container. If it cannot derive it, it returns the alt text.
|
||||
func textOrAltFromCallExpr(ce *ast.CallExpr, alt string) string {
|
||||
text, defined := textFromCallExpr(ce)
|
||||
if !defined {
|
||||
return alt
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
// textFromCallExpr tries to derive the "text" of a Ginkgo spec or container. If
|
||||
// it cannot derive it, it returns false.
|
||||
func textFromCallExpr(ce *ast.CallExpr) (string, bool) {
|
||||
if len(ce.Args) < 1 {
|
||||
return "", false
|
||||
}
|
||||
text, ok := ce.Args[0].(*ast.BasicLit)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
switch text.Kind {
|
||||
case token.CHAR, token.STRING:
|
||||
// For token.CHAR and token.STRING, Value is quoted
|
||||
unquoted, err := strconv.Unquote(text.Value)
|
||||
if err != nil {
|
||||
// If unquoting fails, just use the raw Value
|
||||
return text.Value, true
|
||||
}
|
||||
return unquoted, true
|
||||
default:
|
||||
return text.Value, true
|
||||
}
|
||||
}
|
65
vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/import.go
generated
vendored
Normal file
65
vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/import.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Most of the required functions were available in the
|
||||
// "golang.org/x/tools/go/ast/astutil" package, but not exported.
|
||||
// They were copied from https://github.com/golang/tools/blob/2b0845dc783e36ae26d683f4915a5840ef01ab0f/go/ast/astutil/imports.go
|
||||
|
||||
package outline
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// packageNameForImport returns the package name for the package. If the package
|
||||
// is not imported, it returns nil. "Package name" refers to `pkgname` in the
|
||||
// call expression `pkgname.ExportedIdentifier`. Examples:
|
||||
// (import path not found) -> nil
|
||||
// "import example.com/pkg/foo" -> "foo"
|
||||
// "import fooalias example.com/pkg/foo" -> "fooalias"
|
||||
// "import . example.com/pkg/foo" -> ""
|
||||
func packageNameForImport(f *ast.File, path string) *string {
|
||||
spec := importSpec(f, path)
|
||||
if spec == nil {
|
||||
return nil
|
||||
}
|
||||
name := spec.Name.String()
|
||||
if name == "<nil>" {
|
||||
// If the package name is not explicitly specified,
|
||||
// make an educated guess. This is not guaranteed to be correct.
|
||||
lastSlash := strings.LastIndex(path, "/")
|
||||
if lastSlash == -1 {
|
||||
name = path
|
||||
} else {
|
||||
name = path[lastSlash+1:]
|
||||
}
|
||||
}
|
||||
if name == "." {
|
||||
name = ""
|
||||
}
|
||||
return &name
|
||||
}
|
||||
|
||||
// importSpec returns the import spec if f imports path,
|
||||
// or nil otherwise.
|
||||
func importSpec(f *ast.File, path string) *ast.ImportSpec {
|
||||
for _, s := range f.Imports {
|
||||
if importPath(s) == path {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// importPath returns the unquoted import path of s,
|
||||
// or "" if the path is not properly quoted.
|
||||
func importPath(s *ast.ImportSpec) string {
|
||||
t, err := strconv.Unquote(s.Path.Value)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return t
|
||||
}
|
103
vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/outline.go
generated
vendored
Normal file
103
vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/outline.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
package outline
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
const (
|
||||
// ginkgoImportPath is the well-known ginkgo import path
|
||||
ginkgoImportPath = "github.com/onsi/ginkgo/v2"
|
||||
)
|
||||
|
||||
// FromASTFile returns an outline for a Ginkgo test source file
|
||||
func FromASTFile(fset *token.FileSet, src *ast.File) (*outline, error) {
|
||||
ginkgoPackageName := packageNameForImport(src, ginkgoImportPath)
|
||||
if ginkgoPackageName == nil {
|
||||
return nil, fmt.Errorf("file does not import %q", ginkgoImportPath)
|
||||
}
|
||||
|
||||
root := ginkgoNode{}
|
||||
stack := []*ginkgoNode{&root}
|
||||
ispr := inspector.New([]*ast.File{src})
|
||||
ispr.Nodes([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node, push bool) bool {
|
||||
if push {
|
||||
// Pre-order traversal
|
||||
ce, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
// Because `Nodes` calls this function only when the node is an
|
||||
// ast.CallExpr, this should never happen
|
||||
panic(fmt.Errorf("node starting at %d, ending at %d is not an *ast.CallExpr", node.Pos(), node.End()))
|
||||
}
|
||||
gn, ok := ginkgoNodeFromCallExpr(fset, ce, ginkgoPackageName)
|
||||
if !ok {
|
||||
// Node is not a Ginkgo spec or container, continue
|
||||
return true
|
||||
}
|
||||
parent := stack[len(stack)-1]
|
||||
parent.Nodes = append(parent.Nodes, gn)
|
||||
stack = append(stack, gn)
|
||||
return true
|
||||
}
|
||||
// Post-order traversal
|
||||
start, end := absoluteOffsetsForNode(fset, node)
|
||||
lastVisitedGinkgoNode := stack[len(stack)-1]
|
||||
if start != lastVisitedGinkgoNode.Start || end != lastVisitedGinkgoNode.End {
|
||||
// Node is not a Ginkgo spec or container, so it was not pushed onto the stack, continue
|
||||
return true
|
||||
}
|
||||
stack = stack[0 : len(stack)-1]
|
||||
return true
|
||||
})
|
||||
if len(root.Nodes) == 0 {
|
||||
return &outline{[]*ginkgoNode{}}, nil
|
||||
}
|
||||
|
||||
// Derive the final focused property for all nodes. This must be done
|
||||
// _before_ propagating the inherited focused property.
|
||||
root.BackpropagateUnfocus()
|
||||
// Now, propagate inherited properties, including focused and pending.
|
||||
root.PropagateInheritedProperties()
|
||||
|
||||
return &outline{root.Nodes}, nil
|
||||
}
|
||||
|
||||
type outline struct {
|
||||
Nodes []*ginkgoNode `json:"nodes"`
|
||||
}
|
||||
|
||||
func (o *outline) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(o.Nodes)
|
||||
}
|
||||
|
||||
// String returns a CSV-formatted outline. Spec or container are output in
|
||||
// depth-first order.
|
||||
func (o *outline) String() string {
|
||||
return o.StringIndent(0)
|
||||
}
|
||||
|
||||
// StringIndent returns a CSV-formated outline, but every line is indented by
|
||||
// one 'width' of spaces for every level of nesting.
|
||||
func (o *outline) StringIndent(width int) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("Name,Text,Start,End,Spec,Focused,Pending\n")
|
||||
|
||||
currentIndent := 0
|
||||
pre := func(n *ginkgoNode) {
|
||||
b.WriteString(fmt.Sprintf("%*s", currentIndent, ""))
|
||||
b.WriteString(fmt.Sprintf("%s,%s,%d,%d,%t,%t,%t\n", n.Name, n.Text, n.Start, n.End, n.Spec, n.Focused, n.Pending))
|
||||
currentIndent += width
|
||||
}
|
||||
post := func(n *ginkgoNode) {
|
||||
currentIndent -= width
|
||||
}
|
||||
for _, n := range o.Nodes {
|
||||
n.Walk(pre, post)
|
||||
}
|
||||
return b.String()
|
||||
}
|
98
vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/outline_command.go
generated
vendored
Normal file
98
vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/outline_command.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
package outline
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// indentWidth is the width used by the 'indent' output
|
||||
indentWidth = 4
|
||||
// stdinAlias is a portable alias for stdin. This convention is used in
|
||||
// other CLIs, e.g., kubectl.
|
||||
stdinAlias = "-"
|
||||
usageCommand = "ginkgo outline <filename>"
|
||||
)
|
||||
|
||||
type outlineConfig struct {
|
||||
Format string
|
||||
}
|
||||
|
||||
func BuildOutlineCommand() command.Command {
|
||||
conf := outlineConfig{
|
||||
Format: "csv",
|
||||
}
|
||||
flags, err := types.NewGinkgoFlagSet(
|
||||
types.GinkgoFlags{
|
||||
{Name: "format", KeyPath: "Format",
|
||||
Usage: "Format of outline",
|
||||
UsageArgument: "one of 'csv', 'indent', or 'json'",
|
||||
UsageDefaultValue: conf.Format,
|
||||
},
|
||||
},
|
||||
&conf,
|
||||
types.GinkgoFlagSections{},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return command.Command{
|
||||
Name: "outline",
|
||||
Usage: "ginkgo outline <filename>",
|
||||
ShortDoc: "Create an outline of Ginkgo symbols for a file",
|
||||
Documentation: "To read from stdin, use: `ginkgo outline -`",
|
||||
DocLink: "creating-an-outline-of-specs",
|
||||
Flags: flags,
|
||||
Command: func(args []string, _ []string) {
|
||||
outlineFile(args, conf.Format)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func outlineFile(args []string, format string) {
|
||||
if len(args) != 1 {
|
||||
command.AbortWithUsage("outline expects exactly one argument")
|
||||
}
|
||||
|
||||
filename := args[0]
|
||||
var src *os.File
|
||||
if filename == stdinAlias {
|
||||
src = os.Stdin
|
||||
} else {
|
||||
var err error
|
||||
src, err = os.Open(filename)
|
||||
command.AbortIfError("Failed to open file:", err)
|
||||
}
|
||||
|
||||
fset := token.NewFileSet()
|
||||
|
||||
parsedSrc, err := parser.ParseFile(fset, filename, src, 0)
|
||||
command.AbortIfError("Failed to parse source:", err)
|
||||
|
||||
o, err := FromASTFile(fset, parsedSrc)
|
||||
command.AbortIfError("Failed to create outline:", err)
|
||||
|
||||
var oerr error
|
||||
switch format {
|
||||
case "csv":
|
||||
_, oerr = fmt.Print(o)
|
||||
case "indent":
|
||||
_, oerr = fmt.Print(o.StringIndent(indentWidth))
|
||||
case "json":
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
println(fmt.Sprintf("error marshalling to json: %s", err))
|
||||
}
|
||||
_, oerr = fmt.Println(string(b))
|
||||
default:
|
||||
command.AbortWith("Format %s not accepted", format)
|
||||
}
|
||||
command.AbortIfError("Failed to write outline:", oerr)
|
||||
}
|
230
vendor/github.com/onsi/ginkgo/v2/ginkgo/run/run_command.go
generated
vendored
Normal file
230
vendor/github.com/onsi/ginkgo/v2/ginkgo/run/run_command.go
generated
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
package run
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/formatter"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/internal"
|
||||
"github.com/onsi/ginkgo/v2/internal/interrupt_handler"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
func BuildRunCommand() command.Command {
|
||||
var suiteConfig = types.NewDefaultSuiteConfig()
|
||||
var reporterConfig = types.NewDefaultReporterConfig()
|
||||
var cliConfig = types.NewDefaultCLIConfig()
|
||||
var goFlagsConfig = types.NewDefaultGoFlagsConfig()
|
||||
|
||||
flags, err := types.BuildRunCommandFlagSet(&suiteConfig, &reporterConfig, &cliConfig, &goFlagsConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
interruptHandler := interrupt_handler.NewInterruptHandler(0, nil)
|
||||
interrupt_handler.SwallowSigQuit()
|
||||
|
||||
return command.Command{
|
||||
Name: "run",
|
||||
Flags: flags,
|
||||
Usage: "ginkgo run <FLAGS> <PACKAGES> -- <PASS-THROUGHS>",
|
||||
ShortDoc: "Run the tests in the passed in <PACKAGES> (or the package in the current directory if left blank)",
|
||||
Documentation: "Any arguments after -- will be passed to the test.",
|
||||
DocLink: "running-tests",
|
||||
Command: func(args []string, additionalArgs []string) {
|
||||
var errors []error
|
||||
cliConfig, goFlagsConfig, errors = types.VetAndInitializeCLIAndGoConfig(cliConfig, goFlagsConfig)
|
||||
command.AbortIfErrors("Ginkgo detected configuration issues:", errors)
|
||||
|
||||
runner := &SpecRunner{
|
||||
cliConfig: cliConfig,
|
||||
goFlagsConfig: goFlagsConfig,
|
||||
suiteConfig: suiteConfig,
|
||||
reporterConfig: reporterConfig,
|
||||
flags: flags,
|
||||
|
||||
interruptHandler: interruptHandler,
|
||||
}
|
||||
|
||||
runner.RunSpecs(args, additionalArgs)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type SpecRunner struct {
|
||||
suiteConfig types.SuiteConfig
|
||||
reporterConfig types.ReporterConfig
|
||||
cliConfig types.CLIConfig
|
||||
goFlagsConfig types.GoFlagsConfig
|
||||
flags types.GinkgoFlagSet
|
||||
|
||||
interruptHandler *interrupt_handler.InterruptHandler
|
||||
}
|
||||
|
||||
func (r *SpecRunner) RunSpecs(args []string, additionalArgs []string) {
|
||||
suites := internal.FindSuites(args, r.cliConfig, true)
|
||||
skippedSuites := suites.WithState(internal.TestSuiteStateSkippedByFilter)
|
||||
suites = suites.WithoutState(internal.TestSuiteStateSkippedByFilter)
|
||||
|
||||
if len(skippedSuites) > 0 {
|
||||
fmt.Println("Will skip:")
|
||||
for _, skippedSuite := range skippedSuites {
|
||||
fmt.Println(" " + skippedSuite.Path)
|
||||
}
|
||||
}
|
||||
|
||||
if len(skippedSuites) > 0 && len(suites) == 0 {
|
||||
command.AbortGracefullyWith("All tests skipped! Exiting...")
|
||||
}
|
||||
|
||||
if len(suites) == 0 {
|
||||
command.AbortWith("Found no test suites")
|
||||
}
|
||||
|
||||
if len(suites) > 1 && !r.flags.WasSet("succinct") && r.reporterConfig.Verbosity().LT(types.VerbosityLevelVerbose) {
|
||||
r.reporterConfig.Succinct = true
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
var endTime time.Time
|
||||
if r.suiteConfig.Timeout > 0 {
|
||||
endTime = t.Add(r.suiteConfig.Timeout)
|
||||
}
|
||||
|
||||
iteration := 0
|
||||
OUTER_LOOP:
|
||||
for {
|
||||
if !r.flags.WasSet("seed") {
|
||||
r.suiteConfig.RandomSeed = time.Now().Unix()
|
||||
}
|
||||
if r.cliConfig.RandomizeSuites && len(suites) > 1 {
|
||||
suites = suites.ShuffledCopy(r.suiteConfig.RandomSeed)
|
||||
}
|
||||
|
||||
opc := internal.NewOrderedParallelCompiler(r.cliConfig.ComputedNumCompilers())
|
||||
opc.StartCompiling(suites, r.goFlagsConfig)
|
||||
|
||||
SUITE_LOOP:
|
||||
for {
|
||||
suiteIdx, suite := opc.Next()
|
||||
if suiteIdx >= len(suites) {
|
||||
break SUITE_LOOP
|
||||
}
|
||||
suites[suiteIdx] = suite
|
||||
|
||||
if r.interruptHandler.Status().Interrupted {
|
||||
opc.StopAndDrain()
|
||||
break OUTER_LOOP
|
||||
}
|
||||
|
||||
if suites[suiteIdx].State.Is(internal.TestSuiteStateSkippedDueToEmptyCompilation) {
|
||||
fmt.Printf("Skipping %s (no test files)\n", suite.Path)
|
||||
continue SUITE_LOOP
|
||||
}
|
||||
|
||||
if suites[suiteIdx].State.Is(internal.TestSuiteStateFailedToCompile) {
|
||||
fmt.Println(suites[suiteIdx].CompilationError.Error())
|
||||
if !r.cliConfig.KeepGoing {
|
||||
opc.StopAndDrain()
|
||||
}
|
||||
continue SUITE_LOOP
|
||||
}
|
||||
|
||||
if suites.CountWithState(internal.TestSuiteStateFailureStates...) > 0 && !r.cliConfig.KeepGoing {
|
||||
suites[suiteIdx].State = internal.TestSuiteStateSkippedDueToPriorFailures
|
||||
opc.StopAndDrain()
|
||||
continue SUITE_LOOP
|
||||
}
|
||||
|
||||
if !endTime.IsZero() {
|
||||
r.suiteConfig.Timeout = endTime.Sub(time.Now())
|
||||
if r.suiteConfig.Timeout <= 0 {
|
||||
suites[suiteIdx].State = internal.TestSuiteStateFailedDueToTimeout
|
||||
opc.StopAndDrain()
|
||||
continue SUITE_LOOP
|
||||
}
|
||||
}
|
||||
|
||||
suites[suiteIdx] = internal.RunCompiledSuite(suites[suiteIdx], r.suiteConfig, r.reporterConfig, r.cliConfig, r.goFlagsConfig, additionalArgs)
|
||||
}
|
||||
|
||||
if suites.CountWithState(internal.TestSuiteStateFailureStates...) > 0 {
|
||||
if iteration > 0 {
|
||||
fmt.Printf("\nTests failed on attempt #%d\n\n", iteration+1)
|
||||
}
|
||||
break OUTER_LOOP
|
||||
}
|
||||
|
||||
if r.cliConfig.UntilItFails {
|
||||
fmt.Printf("\nAll tests passed...\nWill keep running them until they fail.\nThis was attempt #%d\n%s\n", iteration+1, orcMessage(iteration+1))
|
||||
} else if r.cliConfig.Repeat > 0 && iteration < r.cliConfig.Repeat {
|
||||
fmt.Printf("\nAll tests passed...\nThis was attempt %d of %d.\n", iteration+1, r.cliConfig.Repeat+1)
|
||||
} else {
|
||||
break OUTER_LOOP
|
||||
}
|
||||
iteration += 1
|
||||
}
|
||||
|
||||
internal.Cleanup(r.goFlagsConfig, suites...)
|
||||
|
||||
messages, err := internal.FinalizeProfilesAndReportsForSuites(suites, r.cliConfig, r.suiteConfig, r.reporterConfig, r.goFlagsConfig)
|
||||
command.AbortIfError("could not finalize profiles:", err)
|
||||
for _, message := range messages {
|
||||
fmt.Println(message)
|
||||
}
|
||||
|
||||
fmt.Printf("\nGinkgo ran %d %s in %s\n", len(suites), internal.PluralizedWord("suite", "suites", len(suites)), time.Since(t))
|
||||
|
||||
if suites.CountWithState(internal.TestSuiteStateFailureStates...) == 0 {
|
||||
if suites.AnyHaveProgrammaticFocus() && 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)
|
||||
command.Abort(command.AbortDetails{ExitCode: types.GINKGO_FOCUS_EXIT_CODE})
|
||||
} else {
|
||||
fmt.Printf("Test Suite Passed\n")
|
||||
command.Abort(command.AbortDetails{})
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(formatter.ColorableStdOut, "")
|
||||
if len(suites) > 1 && suites.CountWithState(internal.TestSuiteStateFailureStates...) > 0 {
|
||||
fmt.Fprintln(formatter.ColorableStdOut,
|
||||
internal.FailedSuitesReport(suites, formatter.NewWithNoColorBool(r.reporterConfig.NoColor)))
|
||||
}
|
||||
fmt.Printf("Test Suite Failed\n")
|
||||
command.Abort(command.AbortDetails{ExitCode: 1})
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
186
vendor/github.com/onsi/ginkgo/v2/ginkgo/unfocus/unfocus_command.go
generated
vendored
Normal file
186
vendor/github.com/onsi/ginkgo/v2/ginkgo/unfocus/unfocus_command.go
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
package unfocus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
)
|
||||
|
||||
func BuildUnfocusCommand() command.Command {
|
||||
return command.Command{
|
||||
Name: "unfocus",
|
||||
Usage: "ginkgo unfocus",
|
||||
ShortDoc: "Recursively unfocus any focused tests under the current directory",
|
||||
DocLink: "filtering-specs",
|
||||
Command: func(_ []string, _ []string) {
|
||||
unfocusSpecs()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func unfocusSpecs() {
|
||||
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 := os.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 := os.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), parser.ParseComments)
|
||||
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 := os.CreateTemp(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 _, eliminationRange := range eliminations {
|
||||
positionToEliminate, lengthToEliminate := eliminationRange[0]-1, eliminationRange[1]
|
||||
if _, err := io.CopyN(to, from, positionToEliminate-cursor); err != nil {
|
||||
return fmt.Errorf("error copying data: %w", err)
|
||||
}
|
||||
|
||||
cursor = positionToEliminate + lengthToEliminate
|
||||
|
||||
if _, err := from.Seek(lengthToEliminate, 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{int64(i.Pos()), 1})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i, ok := n.(*ast.Ident); ok {
|
||||
if i.Name == "Focus" {
|
||||
eliminations = append(eliminations, []int64{int64(i.Pos()), 6})
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return eliminations
|
||||
}
|
||||
|
||||
func isFocus(name string) bool {
|
||||
switch name {
|
||||
case "FDescribe", "FContext", "FIt", "FDescribeTable", "FEntry", "FSpecify", "FWhen":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
22
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/delta.go
generated
vendored
Normal file
22
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/delta.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
75
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/delta_tracker.go
generated
vendored
Normal file
75
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/delta_tracker.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package watch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"regexp"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/internal"
|
||||
)
|
||||
|
||||
type SuiteErrors map[internal.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 internal.TestSuites) (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 internal.TestSuite) error {
|
||||
s, ok := d.suites[suite.Path]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown suite %s", suite.Path)
|
||||
}
|
||||
|
||||
return s.MarkAsRunAndRecomputedDependencies(d.maxDepth)
|
||||
}
|
92
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/dependencies.go
generated
vendored
Normal file
92
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/dependencies.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
||||
}
|
108
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/package_hash.go
generated
vendored
Normal file
108
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/package_hash.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
package watch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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) {
|
||||
entries, err := os.ReadDir(p.path)
|
||||
|
||||
if err != nil {
|
||||
deleted = true
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
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())
|
||||
}
|
85
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/package_hashes.go
generated
vendored
Normal file
85
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/package_hashes.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
87
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/suite.go
generated
vendored
Normal file
87
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/suite.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
package watch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/internal"
|
||||
)
|
||||
|
||||
type Suite struct {
|
||||
Suite internal.TestSuite
|
||||
RunTime time.Time
|
||||
Dependencies Dependencies
|
||||
|
||||
sharedPackageHashes *PackageHashes
|
||||
}
|
||||
|
||||
func NewSuite(suite internal.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)
|
||||
}
|
190
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/watch_command.go
generated
vendored
Normal file
190
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/watch_command.go
generated
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
package watch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/formatter"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/internal"
|
||||
"github.com/onsi/ginkgo/v2/internal/interrupt_handler"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
func BuildWatchCommand() command.Command {
|
||||
var suiteConfig = types.NewDefaultSuiteConfig()
|
||||
var reporterConfig = types.NewDefaultReporterConfig()
|
||||
var cliConfig = types.NewDefaultCLIConfig()
|
||||
var goFlagsConfig = types.NewDefaultGoFlagsConfig()
|
||||
|
||||
flags, err := types.BuildWatchCommandFlagSet(&suiteConfig, &reporterConfig, &cliConfig, &goFlagsConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
interruptHandler := interrupt_handler.NewInterruptHandler(0, nil)
|
||||
interrupt_handler.SwallowSigQuit()
|
||||
|
||||
return command.Command{
|
||||
Name: "watch",
|
||||
Flags: flags,
|
||||
Usage: "ginkgo watch <FLAGS> <PACKAGES> -- <PASS-THROUGHS>",
|
||||
ShortDoc: "Watch the passed in <PACKAGES> and runs their tests whenever changes occur.",
|
||||
Documentation: "Any arguments after -- will be passed to the test.",
|
||||
DocLink: "watching-for-changes",
|
||||
Command: func(args []string, additionalArgs []string) {
|
||||
var errors []error
|
||||
cliConfig, goFlagsConfig, errors = types.VetAndInitializeCLIAndGoConfig(cliConfig, goFlagsConfig)
|
||||
command.AbortIfErrors("Ginkgo detected configuration issues:", errors)
|
||||
|
||||
watcher := &SpecWatcher{
|
||||
cliConfig: cliConfig,
|
||||
goFlagsConfig: goFlagsConfig,
|
||||
suiteConfig: suiteConfig,
|
||||
reporterConfig: reporterConfig,
|
||||
flags: flags,
|
||||
|
||||
interruptHandler: interruptHandler,
|
||||
}
|
||||
|
||||
watcher.WatchSpecs(args, additionalArgs)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type SpecWatcher struct {
|
||||
suiteConfig types.SuiteConfig
|
||||
reporterConfig types.ReporterConfig
|
||||
cliConfig types.CLIConfig
|
||||
goFlagsConfig types.GoFlagsConfig
|
||||
flags types.GinkgoFlagSet
|
||||
|
||||
interruptHandler *interrupt_handler.InterruptHandler
|
||||
}
|
||||
|
||||
func (w *SpecWatcher) WatchSpecs(args []string, additionalArgs []string) {
|
||||
suites := internal.FindSuites(args, w.cliConfig, false).WithoutState(internal.TestSuiteStateSkippedByFilter)
|
||||
|
||||
if len(suites) == 0 {
|
||||
command.AbortWith("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), internal.PluralizedWord("suite", "suites", len(suites)), w.cliConfig.Depth)
|
||||
deltaTracker := NewDeltaTracker(w.cliConfig.Depth, regexp.MustCompile(w.cliConfig.WatchRegExp))
|
||||
delta, errors := deltaTracker.Delta(suites)
|
||||
|
||||
fmt.Printf("Watching %d %s:\n", len(delta.NewSuites), internal.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 {
|
||||
w.updateSeed()
|
||||
w.compileAndRun(suites[0], additionalArgs)
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(time.Second)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
suites := internal.FindSuites(args, w.cliConfig, false).WithoutState(internal.TestSuiteStateSkippedByFilter)
|
||||
delta, _ := deltaTracker.Delta(suites)
|
||||
coloredStream := formatter.ColorableStdOut
|
||||
|
||||
suites = internal.TestSuites{}
|
||||
|
||||
if len(delta.NewSuites) > 0 {
|
||||
fmt.Fprintln(coloredStream, formatter.F("{{green}}Detected %d new %s:{{/}}", len(delta.NewSuites), internal.PluralizedWord("suite", "suites", len(delta.NewSuites))))
|
||||
for _, suite := range delta.NewSuites {
|
||||
suites = append(suites, suite.Suite)
|
||||
fmt.Fprintln(coloredStream, formatter.Fi(1, "%s", suite.Description()))
|
||||
}
|
||||
}
|
||||
|
||||
modifiedSuites := delta.ModifiedSuites()
|
||||
if len(modifiedSuites) > 0 {
|
||||
fmt.Fprintln(coloredStream, formatter.F("{{green}}Detected changes in:{{/}}"))
|
||||
for _, pkg := range delta.ModifiedPackages {
|
||||
fmt.Fprintln(coloredStream, formatter.Fi(1, "%s", pkg))
|
||||
}
|
||||
fmt.Fprintln(coloredStream, formatter.F("{{green}}Will run %d %s:{{/}}", len(modifiedSuites), internal.PluralizedWord("suite", "suites", len(modifiedSuites))))
|
||||
for _, suite := range modifiedSuites {
|
||||
suites = append(suites, suite.Suite)
|
||||
fmt.Fprintln(coloredStream, formatter.Fi(1, "%s", suite.Description()))
|
||||
}
|
||||
fmt.Fprintln(coloredStream, "")
|
||||
}
|
||||
|
||||
if len(suites) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
w.updateSeed()
|
||||
w.computeSuccinctMode(len(suites))
|
||||
for idx := range suites {
|
||||
if w.interruptHandler.Status().Interrupted {
|
||||
return
|
||||
}
|
||||
deltaTracker.WillRun(suites[idx])
|
||||
suites[idx] = w.compileAndRun(suites[idx], additionalArgs)
|
||||
}
|
||||
color := "{{green}}"
|
||||
if suites.CountWithState(internal.TestSuiteStateFailureStates...) > 0 {
|
||||
color = "{{red}}"
|
||||
}
|
||||
fmt.Fprintln(coloredStream, formatter.F(color+"\nDone. Resuming watch...{{/}}"))
|
||||
|
||||
messages, err := internal.FinalizeProfilesAndReportsForSuites(suites, w.cliConfig, w.suiteConfig, w.reporterConfig, w.goFlagsConfig)
|
||||
command.AbortIfError("could not finalize profiles:", err)
|
||||
for _, message := range messages {
|
||||
fmt.Println(message)
|
||||
}
|
||||
case <-w.interruptHandler.Status().Channel:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SpecWatcher) compileAndRun(suite internal.TestSuite, additionalArgs []string) internal.TestSuite {
|
||||
suite = internal.CompileSuite(suite, w.goFlagsConfig)
|
||||
if suite.State.Is(internal.TestSuiteStateFailedToCompile) {
|
||||
fmt.Println(suite.CompilationError.Error())
|
||||
return suite
|
||||
}
|
||||
if w.interruptHandler.Status().Interrupted {
|
||||
return suite
|
||||
}
|
||||
suite = internal.RunCompiledSuite(suite, w.suiteConfig, w.reporterConfig, w.cliConfig, w.goFlagsConfig, additionalArgs)
|
||||
internal.Cleanup(w.goFlagsConfig, suite)
|
||||
return suite
|
||||
}
|
||||
|
||||
func (w *SpecWatcher) computeSuccinctMode(numSuites int) {
|
||||
if w.reporterConfig.Verbosity().GTE(types.VerbosityLevelVerbose) {
|
||||
w.reporterConfig.Succinct = false
|
||||
return
|
||||
}
|
||||
|
||||
if w.flags.WasSet("succinct") {
|
||||
return
|
||||
}
|
||||
|
||||
if numSuites == 1 {
|
||||
w.reporterConfig.Succinct = false
|
||||
}
|
||||
|
||||
if numSuites > 1 {
|
||||
w.reporterConfig.Succinct = true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SpecWatcher) updateSeed() {
|
||||
if !w.flags.WasSet("seed") {
|
||||
w.suiteConfig.RandomSeed = time.Now().Unix()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user