dependencies: ginkgo v2.15.0, gomega v1.31.0

The main reason for updating is support for reporting the cause of context
cancellation: Ginkgo provides that information when canceling a context and
Gomega polling code includes that when generating a failure message.
This commit is contained in:
Patrick Ohly
2024-01-18 12:45:55 +01:00
parent 909faa3a9b
commit 18f0af1f00
51 changed files with 421 additions and 170 deletions

View File

@@ -1,3 +1,46 @@
## 2.15.0
### Features
- JUnit reports now interpret Label(owner:X) and set owner to X. [8f3bd70]
- include cancellation reason when cancelling spec context [96e915c]
### Fixes
- emit output of failed go tool cover invocation so users can try to debug things for themselves [c245d09]
- fix outline when using nodot in ginkgo v2 [dca77c8]
- Document areas where GinkgoT() behaves differently from testing.T [dbaf18f]
- bugfix(docs): use Unsetenv instead of Clearenv (#1337) [6f67a14]
### Maintenance
- Bump to go 1.20 [4fcd0b3]
## 2.14.0
### Features
You can now use `GinkgoTB()` when you need an instance of `testing.TB` to pass to a library.
Prior to this release table testing only supported generating individual `It`s for each test entry. `DescribeTableSubtree` extends table testing support to entire testing subtrees - under the hood `DescrieTableSubtree` generates a new container for each entry and invokes your function to fill our the container. See the [docs](https://onsi.github.io/ginkgo/#generating-subtree-tables) to learn more.
- Introduce DescribeTableSubtree [65ec56d]
- add GinkgoTB() to docs [4a2c832]
- Add GinkgoTB() function (#1333) [92b6744]
### Fixes
- Fix typo in internal/suite.go (#1332) [beb9507]
- Fix typo in docs/index.md (#1319) [4ac3a13]
- allow wasm to compile with ginkgo present (#1311) [b2e5bc5]
### Maintenance
- Bump golang.org/x/tools from 0.16.0 to 0.16.1 (#1316) [465a8ec]
- Bump actions/setup-go from 4 to 5 (#1313) [eab0e40]
- Bump github/codeql-action from 2 to 3 (#1317) [fbf9724]
- Bump golang.org/x/crypto (#1318) [3ee80ee]
- Bump golang.org/x/tools from 0.14.0 to 0.16.0 (#1306) [123e1d5]
- Bump github.com/onsi/gomega from 1.29.0 to 1.30.0 (#1297) [558f6e0]
- Bump golang.org/x/net from 0.17.0 to 0.19.0 (#1307) [84ff7f3]
## 2.13.2
### Fixes

View File

@@ -144,7 +144,7 @@ func FinalizeProfilesAndReportsForSuites(suites TestSuites, cliConfig types.CLIC
return messages, nil
}
//loads each profile, combines them, deletes them, stores them in destination
// 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`)
@@ -184,7 +184,7 @@ 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())
return 0, fmt.Errorf("Could not process Coverprofile %s: %s - %s", profile, err.Error(), string(output))
}
re := regexp.MustCompile(`total:\s*\(statements\)\s*(\d*\.\d*)\%`)
matches := re.FindStringSubmatch(string(output))

View File

@@ -1,10 +1,11 @@
package outline
import (
"github.com/onsi/ginkgo/v2/types"
"go/ast"
"go/token"
"strconv"
"github.com/onsi/ginkgo/v2/types"
)
const (

View File

@@ -28,14 +28,7 @@ func packageNameForImport(f *ast.File, path string) *string {
}
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:]
}
name = "ginkgo"
}
if name == "." {
name = ""

View File

@@ -1,7 +1,10 @@
package ginkgo
import (
"testing"
"github.com/onsi/ginkgo/v2/internal/testingtproxy"
"github.com/onsi/ginkgo/v2/types"
)
/*
@@ -12,10 +15,15 @@ GinkgoT() is analogous to *testing.T and implements the majority of *testing.T's
GinkgoT() takes an optional offset argument that can be used to get the
correct line number associated with the failure - though you do not need to use this if you call GinkgoHelper() or GinkgoT().Helper() appropriately
GinkgoT() attempts to mimic the behavior of `testing.T` with the exception of the following:
- Error/Errorf: failures in Ginkgo always immediately stop execution and there is no mechanism to log a failure without aborting the test. As such Error/Errorf are equivalent to Fatal/Fatalf.
- Parallel() is a no-op as Ginkgo's multi-process parallelism model is substantially different from go test's in-process model.
You can learn more here: https://onsi.github.io/ginkgo/#using-third-party-libraries
*/
func GinkgoT(optionalOffset ...int) FullGinkgoTInterface {
offset := 3
offset := 1
if len(optionalOffset) > 0 {
offset = optionalOffset[0]
}
@@ -41,21 +49,21 @@ The portion of the interface returned by GinkgoT() that maps onto methods in the
type GinkgoTInterface interface {
Cleanup(func())
Setenv(kev, value string)
Error(args ...interface{})
Errorf(format string, args ...interface{})
Error(args ...any)
Errorf(format string, args ...any)
Fail()
FailNow()
Failed() bool
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
Fatal(args ...any)
Fatalf(format string, args ...any)
Helper()
Log(args ...interface{})
Logf(format string, args ...interface{})
Log(args ...any)
Logf(format string, args ...any)
Name() string
Parallel()
Skip(args ...interface{})
Skip(args ...any)
SkipNow()
Skipf(format string, args ...interface{})
Skipf(format string, args ...any)
Skipped() bool
TempDir() string
}
@@ -71,9 +79,9 @@ type FullGinkgoTInterface interface {
AddReportEntryVisibilityNever(name string, args ...any)
//Prints to the GinkgoWriter
Print(a ...interface{})
Printf(format string, a ...interface{})
Println(a ...interface{})
Print(a ...any)
Printf(format string, a ...any)
Println(a ...any)
//Provides access to Ginkgo's color formatting, correctly configured to match the color settings specified in the invocation of ginkgo
F(format string, args ...any) string
@@ -92,3 +100,81 @@ type FullGinkgoTInterface interface {
AttachProgressReporter(func() string) func()
}
/*
GinkgoTB() implements a wrapper that exactly matches the testing.TB interface.
In go 1.18 a new private() function was added to the testing.TB interface. Any function which accepts testing.TB as input needs to be passed in something that directly implements testing.TB.
This wrapper satisfies the testing.TB interface and intended to be used as a drop-in replacement with third party libraries that accept testing.TB.
Similar to GinkgoT(), GinkgoTB() takes an optional offset argument that can be used to get the
correct line number associated with the failure - though you do not need to use this if you call GinkgoHelper() or GinkgoT().Helper() appropriately
*/
func GinkgoTB(optionalOffset ...int) *GinkgoTBWrapper {
offset := 2
if len(optionalOffset) > 0 {
offset = optionalOffset[0]
}
return &GinkgoTBWrapper{GinkgoT: GinkgoT(offset)}
}
type GinkgoTBWrapper struct {
testing.TB
GinkgoT FullGinkgoTInterface
}
func (g *GinkgoTBWrapper) Cleanup(f func()) {
g.GinkgoT.Cleanup(f)
}
func (g *GinkgoTBWrapper) Error(args ...any) {
g.GinkgoT.Error(args...)
}
func (g *GinkgoTBWrapper) Errorf(format string, args ...any) {
g.GinkgoT.Errorf(format, args...)
}
func (g *GinkgoTBWrapper) Fail() {
g.GinkgoT.Fail()
}
func (g *GinkgoTBWrapper) FailNow() {
g.GinkgoT.FailNow()
}
func (g *GinkgoTBWrapper) Failed() bool {
return g.GinkgoT.Failed()
}
func (g *GinkgoTBWrapper) Fatal(args ...any) {
g.GinkgoT.Fatal(args...)
}
func (g *GinkgoTBWrapper) Fatalf(format string, args ...any) {
g.GinkgoT.Fatalf(format, args...)
}
func (g *GinkgoTBWrapper) Helper() {
types.MarkAsHelper(1)
}
func (g *GinkgoTBWrapper) Log(args ...any) {
g.GinkgoT.Log(args...)
}
func (g *GinkgoTBWrapper) Logf(format string, args ...any) {
g.GinkgoT.Logf(format, args...)
}
func (g *GinkgoTBWrapper) Name() string {
return g.GinkgoT.Name()
}
func (g *GinkgoTBWrapper) Setenv(key, value string) {
g.GinkgoT.Setenv(key, value)
}
func (g *GinkgoTBWrapper) Skip(args ...any) {
g.GinkgoT.Skip(args...)
}
func (g *GinkgoTBWrapper) SkipNow() {
g.GinkgoT.SkipNow()
}
func (g *GinkgoTBWrapper) Skipf(format string, args ...any) {
g.GinkgoT.Skipf(format, args...)
}
func (g *GinkgoTBWrapper) Skipped() bool {
return g.GinkgoT.Skipped()
}
func (g *GinkgoTBWrapper) TempDir() string {
return g.GinkgoT.TempDir()
}

View File

@@ -0,0 +1,7 @@
//go:build wasm
package internal
func NewOutputInterceptor() OutputInterceptor {
return &NoopOutputInterceptor{}
}

View File

@@ -0,0 +1,10 @@
//go:build wasm
package internal
import (
"os"
"syscall"
)
var PROGRESS_SIGNALS = []os.Signal{syscall.SIGUSR1}

View File

@@ -17,7 +17,7 @@ type specContext struct {
context.Context
*ProgressReporterManager
cancel context.CancelFunc
cancel context.CancelCauseFunc
suite *Suite
}
@@ -30,7 +30,7 @@ Note that while SpecContext is used to enforce deadlines by Ginkgo it is not con
This is because Ginkgo needs finer control over when the context is canceled. Specifically, Ginkgo needs to generate a ProgressReport before it cancels the context to ensure progress is captured where the spec is currently running. The only way to avoid a race here is to manually control the cancellation.
*/
func NewSpecContext(suite *Suite) *specContext {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancelCause(context.Background())
sc := &specContext{
cancel: cancel,
suite: suite,

View File

@@ -79,7 +79,7 @@ func NewSuite() *Suite {
func (suite *Suite) Clone() (*Suite, error) {
if suite.phase != PhaseBuildTopLevel {
return nil, fmt.Errorf("cnanot clone suite after tree has been built")
return nil, fmt.Errorf("cannot clone suite after tree has been built")
}
return &Suite{
tree: &TreeNode{},
@@ -858,7 +858,7 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ
}
sc := NewSpecContext(suite)
defer sc.cancel()
defer sc.cancel(fmt.Errorf("spec has finished"))
suite.selectiveLock.Lock()
suite.currentSpecContext = sc
@@ -958,7 +958,7 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ
// tell the spec to stop. it's important we generate the progress report first to make sure we capture where
// the spec is actually stuck
sc.cancel()
sc.cancel(fmt.Errorf("%s timeout occurred", timeoutInPlay))
//and now we wait for the grace period
gracePeriodChannel = time.After(gracePeriod)
case <-interruptStatus.Channel:
@@ -985,7 +985,7 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ
}
progressReport = progressReport.WithoutOtherGoroutines()
sc.cancel()
sc.cancel(fmt.Errorf(interruptStatus.Message()))
if interruptStatus.Level == interrupt_handler.InterruptLevelBailOut {
if interruptStatus.ShouldIncludeProgressReport() {

View File

@@ -15,6 +15,7 @@ import (
"fmt"
"os"
"path"
"regexp"
"strings"
"github.com/onsi/ginkgo/v2/config"
@@ -104,6 +105,8 @@ type JUnitProperty struct {
Value string `xml:"value,attr"`
}
var ownerRE = regexp.MustCompile(`(?i)^owner:(.*)$`)
type JUnitTestCase struct {
// Name maps onto the full text of the spec - equivalent to "[SpecReport.LeafNodeType] SpecReport.FullText()"
Name string `xml:"name,attr"`
@@ -113,6 +116,8 @@ type JUnitTestCase struct {
Status string `xml:"status,attr"`
// Time is the time in seconds to execute the spec - maps onto SpecReport.RunTime
Time float64 `xml:"time,attr"`
// Owner is the owner the spec - is set if a label matching Label("owner:X") is provided. The last matching label is used as the owner, thereby allowing specs to override owners specified in container nodes.
Owner string `xml:"owner,attr,omitempty"`
//Skipped is populated with a message if the test was skipped or pending
Skipped *JUnitSkipped `xml:"skipped,omitempty"`
//Error is populated if the test panicked or was interrupted
@@ -195,6 +200,12 @@ func GenerateJUnitReportWithConfig(report types.Report, dst string, config Junit
if len(labels) > 0 && !config.OmitSpecLabels {
name = name + " [" + strings.Join(labels, ", ") + "]"
}
owner := ""
for _, label := range labels {
if matches := ownerRE.FindStringSubmatch(label); len(matches) == 2 {
owner = matches[1]
}
}
name = strings.TrimSpace(name)
test := JUnitTestCase{
@@ -202,6 +213,7 @@ func GenerateJUnitReportWithConfig(report types.Report, dst string, config Junit
Classname: report.SuiteDescription,
Status: spec.State.String(),
Time: spec.RunTime.Seconds(),
Owner: owner,
}
if !spec.State.Is(config.OmitTimelinesForSpecState) {
test.SystemErr = systemErrForUnstructuredReporters(spec)

View File

@@ -46,7 +46,7 @@ And can explore some Table patterns here: https://onsi.github.io/ginkgo/#table-s
*/
func DescribeTable(description string, args ...interface{}) bool {
GinkgoHelper()
generateTable(description, args...)
generateTable(description, false, args...)
return true
}
@@ -56,7 +56,7 @@ You can focus a table with `FDescribeTable`. This is equivalent to `FDescribe`.
func FDescribeTable(description string, args ...interface{}) bool {
GinkgoHelper()
args = append(args, internal.Focus)
generateTable(description, args...)
generateTable(description, false, args...)
return true
}
@@ -66,7 +66,7 @@ You can mark a table as pending with `PDescribeTable`. This is equivalent to `P
func PDescribeTable(description string, args ...interface{}) bool {
GinkgoHelper()
args = append(args, internal.Pending)
generateTable(description, args...)
generateTable(description, false, args...)
return true
}
@@ -75,6 +75,71 @@ You can mark a table as pending with `XDescribeTable`. This is equivalent to `X
*/
var XDescribeTable = PDescribeTable
/*
DescribeTableSubtree describes a table-driven spec that generates a set of tests for each entry.
For example:
DescribeTableSubtree("a subtree table",
func(url string, code int, message string) {
var resp *http.Response
BeforeEach(func() {
var err error
resp, err = http.Get(url)
Expect(err).NotTo(HaveOccurred())
DeferCleanup(resp.Body.Close)
})
It("should return the expected status code", func() {
Expect(resp.StatusCode).To(Equal(code))
})
It("should return the expected message", func() {
body, err := ioutil.ReadAll(resp.Body)
Expect(err).NotTo(HaveOccurred())
Expect(string(body)).To(Equal(message))
})
},
Entry("default response", "example.com/response", http.StatusOK, "hello world"),
Entry("missing response", "example.com/missing", http.StatusNotFound, "wat?"),
)
Note that you **must** place define an It inside the body function.
You can learn more about DescribeTableSubtree here: https://onsi.github.io/ginkgo/#table-specs
And can explore some Table patterns here: https://onsi.github.io/ginkgo/#table-specs-patterns
*/
func DescribeTableSubtree(description string, args ...interface{}) bool {
GinkgoHelper()
generateTable(description, true, args...)
return true
}
/*
You can focus a table with `FDescribeTableSubtree`. This is equivalent to `FDescribe`.
*/
func FDescribeTableSubtree(description string, args ...interface{}) bool {
GinkgoHelper()
args = append(args, internal.Focus)
generateTable(description, true, args...)
return true
}
/*
You can mark a table as pending with `PDescribeTableSubtree`. This is equivalent to `PDescribe`.
*/
func PDescribeTableSubtree(description string, args ...interface{}) bool {
GinkgoHelper()
args = append(args, internal.Pending)
generateTable(description, true, args...)
return true
}
/*
You can mark a table as pending with `XDescribeTableSubtree`. This is equivalent to `XDescribe`.
*/
var XDescribeTableSubtree = PDescribeTableSubtree
/*
TableEntry represents an entry in a table test. You generally use the `Entry` constructor.
*/
@@ -131,14 +196,14 @@ var XEntry = PEntry
var contextType = reflect.TypeOf(new(context.Context)).Elem()
var specContextType = reflect.TypeOf(new(SpecContext)).Elem()
func generateTable(description string, args ...interface{}) {
func generateTable(description string, isSubtree bool, args ...interface{}) {
GinkgoHelper()
cl := types.NewCodeLocation(0)
containerNodeArgs := []interface{}{cl}
entries := []TableEntry{}
var itBody interface{}
var itBodyType reflect.Type
var internalBody interface{}
var internalBodyType reflect.Type
var tableLevelEntryDescription interface{}
tableLevelEntryDescription = func(args ...interface{}) string {
@@ -166,11 +231,11 @@ func generateTable(description string, args ...interface{}) {
case t.Kind() == reflect.Func && t.NumOut() == 1 && t.Out(0) == reflect.TypeOf(""):
tableLevelEntryDescription = arg
case t.Kind() == reflect.Func:
if itBody != nil {
if internalBody != nil {
exitIfErr(types.GinkgoErrors.MultipleEntryBodyFunctionsForTable(cl))
}
itBody = arg
itBodyType = reflect.TypeOf(itBody)
internalBody = arg
internalBodyType = reflect.TypeOf(internalBody)
default:
containerNodeArgs = append(containerNodeArgs, arg)
}
@@ -200,39 +265,47 @@ func generateTable(description string, args ...interface{}) {
err = types.GinkgoErrors.InvalidEntryDescription(entry.codeLocation)
}
itNodeArgs := []interface{}{entry.codeLocation}
itNodeArgs = append(itNodeArgs, entry.decorations...)
internalNodeArgs := []interface{}{entry.codeLocation}
internalNodeArgs = append(internalNodeArgs, entry.decorations...)
hasContext := false
if itBodyType.NumIn() > 0. {
if itBodyType.In(0).Implements(specContextType) {
if internalBodyType.NumIn() > 0. {
if internalBodyType.In(0).Implements(specContextType) {
hasContext = true
} else if itBodyType.In(0).Implements(contextType) && (len(entry.parameters) == 0 || !reflect.TypeOf(entry.parameters[0]).Implements(contextType)) {
} else if internalBodyType.In(0).Implements(contextType) && (len(entry.parameters) == 0 || !reflect.TypeOf(entry.parameters[0]).Implements(contextType)) {
hasContext = true
}
}
if err == nil {
err = validateParameters(itBody, entry.parameters, "Table Body function", entry.codeLocation, hasContext)
err = validateParameters(internalBody, entry.parameters, "Table Body function", entry.codeLocation, hasContext)
}
if hasContext {
itNodeArgs = append(itNodeArgs, func(c SpecContext) {
internalNodeArgs = append(internalNodeArgs, func(c SpecContext) {
if err != nil {
panic(err)
}
invokeFunction(itBody, append([]interface{}{c}, entry.parameters...))
invokeFunction(internalBody, append([]interface{}{c}, entry.parameters...))
})
if isSubtree {
exitIfErr(types.GinkgoErrors.ContextsCannotBeUsedInSubtreeTables(cl))
}
} else {
itNodeArgs = append(itNodeArgs, func() {
internalNodeArgs = append(internalNodeArgs, func() {
if err != nil {
panic(err)
}
invokeFunction(itBody, entry.parameters)
invokeFunction(internalBody, entry.parameters)
})
}
pushNode(internal.NewNode(deprecationTracker, types.NodeTypeIt, description, itNodeArgs...))
internalNodeType := types.NodeTypeIt
if isSubtree {
internalNodeType = types.NodeTypeContainer
}
pushNode(internal.NewNode(deprecationTracker, internalNodeType, description, internalNodeArgs...))
}
})

View File

@@ -505,6 +505,15 @@ func (g ginkgoErrors) IncorrectVariadicParameterTypeToTableFunction(expected, ac
}
}
func (g ginkgoErrors) ContextsCannotBeUsedInSubtreeTables(cl CodeLocation) error {
return GinkgoError{
Heading: "Contexts cannot be used in subtree tables",
Message: "You''ve defined a subtree body function that accepts a context but did not provide one in the table entry. Ginkgo SpecContexts can only be passed in to subject and setup nodes - so if you are trying to implement a spec timeout you should request a context in the It function within your subtree body function, not in the subtree body function itself.",
CodeLocation: cl,
DocLink: "table-specs",
}
}
/* Parallel Synchronization errors */
func (g ginkgoErrors) AggregatedReportUnavailableDueToNodeDisappearing() error {

View File

@@ -1,3 +1,3 @@
package types
const VERSION = "2.13.2"
const VERSION = "2.15.0"

View File

@@ -1,3 +1,17 @@
## 1.31.0
### Features
- Async assertions include context cancellation cause if present [121c37f]
### Maintenance
- Bump minimum go version [dee1e3c]
- docs: fix typo in example usage "occured" -> "occurred" [49005fe]
- Bump actions/setup-go from 4 to 5 (#714) [f1c8757]
- Bump github/codeql-action from 2 to 3 (#715) [9836e76]
- Bump github.com/onsi/ginkgo/v2 from 2.13.0 to 2.13.2 (#713) [54726f0]
- Bump golang.org/x/net from 0.17.0 to 0.19.0 (#711) [df97ecc]
- docs: fix `HaveExactElement` typo (#712) [a672c86]
## 1.30.0
### Features

View File

@@ -22,7 +22,7 @@ import (
"github.com/onsi/gomega/types"
)
const GOMEGA_VERSION = "1.30.0"
const GOMEGA_VERSION = "1.31.0"
const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler.
If you're using Ginkgo then you probably forgot to put your assertion in an It().

View File

@@ -553,7 +553,12 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch
lock.Unlock()
}
case <-contextDone:
fail("Context was cancelled")
err := context.Cause(assertion.ctx)
if err != nil && err != context.Canceled {
fail(fmt.Sprintf("Context was cancelled (cause: %s)", err))
} else {
fail("Context was cancelled")
}
return false
case <-timeout:
if assertion.asyncType == AsyncAssertionTypeEventually {

View File

@@ -394,7 +394,7 @@ func ConsistOf(elements ...interface{}) types.GomegaMatcher {
}
}
// HaveExactElemets succeeds if actual contains elements that precisely match the elemets passed into the matcher. The ordering of the elements does matter.
// HaveExactElements succeeds if actual contains elements that precisely match the elemets passed into the matcher. The ordering of the elements does matter.
// By default HaveExactElements() uses Equal() to match the elements, however custom matchers can be passed in instead. Here are some examples:
//
// Expect([]string{"Foo", "FooBar"}).Should(HaveExactElements("Foo", "FooBar"))