diff --git a/core_dsl.go b/core_dsl.go index 8b7b6685c..9761f68ec 100644 --- a/core_dsl.go +++ b/core_dsl.go @@ -540,7 +540,7 @@ and will simply log the passed in text to the GinkgoWriter. If By is handed a f By will also generate and attach a ReportEntry to the spec. This will ensure that By annotations appear in Ginkgo's machine-readable reports. -Note that By does not generate a new Ginkgo node - rather it is simply synctactic sugar around GinkgoWriter and AddReportEntry +Note that By does not generate a new Ginkgo node - rather it is simply syntactic sugar around GinkgoWriter and AddReportEntry You can learn more about By here: https://onsi.github.io/ginkgo/#documenting-complex-specs-by */ func By(text string, callback ...func()) { diff --git a/docs/index.md b/docs/index.md index 1a94a249c..83b93cea2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5340,6 +5340,8 @@ Since `GinkgoT()` implements `Cleanup()` (using `DeferCleanup()` under the hood) When using Gomock you may want to run `ginkgo` with the `-trace` flag to print out stack traces for failures which will help you trace down where, in your code, invalid calls occurred. +`GinkgoT()` also provides additional methods that are Ginkgo-specific. This allows rich third-party integrations to be built on top of Ginkgo - with GinkgoT() serving as a single connection point. + ### IDE Support Ginkgo works best from the command-line, and [`ginkgo watch`](#watching-for-changes) makes it easy to rerun tests on the command line whenever changes are detected. diff --git a/dsl/core/core_dsl.go b/dsl/core/core_dsl.go index 927e312cf..06c8637a2 100644 --- a/dsl/core/core_dsl.go +++ b/dsl/core/core_dsl.go @@ -21,6 +21,7 @@ const GINKGO_VERSION = ginkgo.GINKGO_VERSION type GinkgoWriterInterface = ginkgo.GinkgoWriterInterface type GinkgoTestingT = ginkgo.GinkgoTestingT type GinkgoTInterface = ginkgo.GinkgoTInterface +type FullGinkgoTInterface = ginkgo.FullGinkgoTInterface type SpecContext = ginkgo.SpecContext var GinkgoWriter = ginkgo.GinkgoWriter diff --git a/ginkgo_t_dsl.go b/ginkgo_t_dsl.go index c5a7eb94d..2f1927006 100644 --- a/ginkgo_t_dsl.go +++ b/ginkgo_t_dsl.go @@ -1,22 +1,38 @@ package ginkgo -import "github.com/onsi/ginkgo/v2/internal/testingtproxy" +import ( + "github.com/onsi/ginkgo/v2/internal/testingtproxy" +) /* -GinkgoT() implements an interface analogous to *testing.T and can be used with -third-party libraries that accept *testing.T through an interface. +GinkgoT() implements an interface that allows third party libraries to integrate with and build on top of Ginkgo. + +GinkgoT() is analogous to *testing.T and implements the majority of *testing.T's methods. It can be typically be used a a drop-in replacement with third-party libraries that accept *testing.T through an interface. 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 You can learn more here: https://onsi.github.io/ginkgo/#using-third-party-libraries */ -func GinkgoT(optionalOffset ...int) GinkgoTInterface { +func GinkgoT(optionalOffset ...int) FullGinkgoTInterface { offset := 3 if len(optionalOffset) > 0 { offset = optionalOffset[0] } - return testingtproxy.New(GinkgoWriter, Fail, Skip, DeferCleanup, CurrentSpecReport, offset) + return testingtproxy.New( + GinkgoWriter, + Fail, + Skip, + DeferCleanup, + CurrentSpecReport, + AddReportEntry, + GinkgoRecover, + AttachProgressReporter, + suiteConfig.RandomSeed, + suiteConfig.ParallelProcess, + suiteConfig.ParallelTotal, + reporterConfig.NoColor, + offset) } /* @@ -43,3 +59,30 @@ type GinkgoTInterface interface { Skipped() bool TempDir() string } + +type FullGinkgoTInterface interface { + GinkgoTInterface + + AddReportEntryVisibilityAlways(name string, args ...any) + AddReportEntryVisibilityFailureOrVerbose(name string, args ...any) + AddReportEntryVisibilityNever(name string, args ...any) + + //Prints to the GinkgoWriter + Print(a ...interface{}) + Printf(format string, a ...interface{}) + Println(a ...interface{}) + + //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 + Fi(indentation uint, format string, args ...any) string + Fiw(indentation uint, maxWidth uint, format string, args ...any) string + + GinkgoRecover() + DeferCleanup(args ...any) + + RandomSeed() int64 + ParallelProcess() int + ParallelTotal() int + + AttachProgressReporter(func() string) func() +} diff --git a/internal/testingtproxy/testing_t_proxy.go b/internal/testingtproxy/testing_t_proxy.go index c797c95d4..92acc0a00 100644 --- a/internal/testingtproxy/testing_t_proxy.go +++ b/internal/testingtproxy/testing_t_proxy.go @@ -5,34 +5,61 @@ import ( "io" "os" + "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/types" ) type failFunc func(message string, callerSkip ...int) type skipFunc func(message string, callerSkip ...int) -type cleanupFunc func(args ...interface{}) +type cleanupFunc func(args ...any) type reportFunc func() types.SpecReport +type addReportEntryFunc func(names string, args ...any) +type ginkgoWriterInterface interface { + io.Writer -func New(writer io.Writer, fail failFunc, skip skipFunc, cleanup cleanupFunc, report reportFunc, offset int) *ginkgoTestingTProxy { + Print(a ...interface{}) + Printf(format string, a ...interface{}) + Println(a ...interface{}) +} +type ginkgoRecoverFunc func() +type attachProgressReporterFunc func(func() string) func() + +func New(writer ginkgoWriterInterface, fail failFunc, skip skipFunc, cleanup cleanupFunc, report reportFunc, addReportEntry addReportEntryFunc, ginkgoRecover ginkgoRecoverFunc, attachProgressReporter attachProgressReporterFunc, randomSeed int64, parallelProcess int, parallelTotal int, noColor bool, offset int) *ginkgoTestingTProxy { return &ginkgoTestingTProxy{ - fail: fail, - offset: offset, - writer: writer, - skip: skip, - cleanup: cleanup, - report: report, + fail: fail, + offset: offset, + writer: writer, + skip: skip, + cleanup: cleanup, + report: report, + addReportEntry: addReportEntry, + ginkgoRecover: ginkgoRecover, + attachProgressReporter: attachProgressReporter, + randomSeed: randomSeed, + parallelProcess: parallelProcess, + parallelTotal: parallelTotal, + f: formatter.NewWithNoColorBool(noColor), } } type ginkgoTestingTProxy struct { - fail failFunc - skip skipFunc - cleanup cleanupFunc - report reportFunc - offset int - writer io.Writer -} + fail failFunc + skip skipFunc + cleanup cleanupFunc + report reportFunc + offset int + writer ginkgoWriterInterface + addReportEntry addReportEntryFunc + ginkgoRecover ginkgoRecoverFunc + attachProgressReporter attachProgressReporterFunc + randomSeed int64 + parallelProcess int + parallelTotal int + f formatter.Formatter +} + +// basic testing.T support func (t *ginkgoTestingTProxy) Cleanup(f func()) { t.cleanup(f, internal.Offset(1)) @@ -126,3 +153,54 @@ func (t *ginkgoTestingTProxy) TempDir() string { return tmpDir } + +// FullGinkgoTInterface +func (t *ginkgoTestingTProxy) AddReportEntryVisibilityAlways(name string, args ...any) { + finalArgs := []any{internal.Offset(1), types.ReportEntryVisibilityAlways} + t.addReportEntry(name, append(finalArgs, args...)...) +} +func (t *ginkgoTestingTProxy) AddReportEntryVisibilityFailureOrVerbose(name string, args ...any) { + finalArgs := []any{internal.Offset(1), types.ReportEntryVisibilityFailureOrVerbose} + t.addReportEntry(name, append(finalArgs, args...)...) +} +func (t *ginkgoTestingTProxy) AddReportEntryVisibilityNever(name string, args ...any) { + finalArgs := []any{internal.Offset(1), types.ReportEntryVisibilityNever} + t.addReportEntry(name, append(finalArgs, args...)...) +} +func (t *ginkgoTestingTProxy) Print(a ...any) { + t.writer.Print(a...) +} +func (t *ginkgoTestingTProxy) Printf(format string, a ...any) { + t.writer.Printf(format, a...) +} +func (t *ginkgoTestingTProxy) Println(a ...any) { + t.writer.Println(a...) +} +func (t *ginkgoTestingTProxy) F(format string, args ...any) string { + return t.f.F(format, args...) +} +func (t *ginkgoTestingTProxy) Fi(indentation uint, format string, args ...any) string { + return t.f.Fi(indentation, format, args...) +} +func (t *ginkgoTestingTProxy) Fiw(indentation uint, maxWidth uint, format string, args ...any) string { + return t.f.Fiw(indentation, maxWidth, format, args...) +} +func (t *ginkgoTestingTProxy) GinkgoRecover() { + t.ginkgoRecover() +} +func (t *ginkgoTestingTProxy) DeferCleanup(args ...any) { + finalArgs := []any{internal.Offset(1)} + t.cleanup(append(finalArgs, args...)...) +} +func (t *ginkgoTestingTProxy) RandomSeed() int64 { + return t.randomSeed +} +func (t *ginkgoTestingTProxy) ParallelProcess() int { + return t.parallelProcess +} +func (t *ginkgoTestingTProxy) ParallelTotal() int { + return t.parallelTotal +} +func (t *ginkgoTestingTProxy) AttachProgressReporter(f func() string) func() { + return t.attachProgressReporter(f) +} diff --git a/internal/testingtproxy/testingtproxy_test.go b/internal/testingtproxy/testingtproxy_test.go index 4fa3de8b7..35dbe497e 100644 --- a/internal/testingtproxy/testingtproxy_test.go +++ b/internal/testingtproxy/testingtproxy_test.go @@ -9,6 +9,7 @@ import ( "github.com/onsi/gomega/gbytes" + "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/internal/testingtproxy" "github.com/onsi/ginkgo/v2/types" ) @@ -19,7 +20,7 @@ type messagedCall struct { } var _ = Describe("Testingtproxy", func() { - var t GinkgoTInterface + var t FullGinkgoTInterface var failFunc func(message string, callerSkip ...int) var skipFunc func(message string, callerSkip ...int) @@ -30,8 +31,14 @@ var _ = Describe("Testingtproxy", func() { var offset int var reportToReturn types.SpecReport var buf *gbytes.Buffer + var recoverCall bool + + var attachedProgressReporter func() string + var attachProgressReporterCancelCalled bool BeforeEach(func() { + recoverCall = false + attachProgressReporterCancelCalled = false failFuncCall = messagedCall{} skipFuncCall = messagedCall{} offset = 3 @@ -50,10 +57,33 @@ var _ = Describe("Testingtproxy", func() { reportFunc = func() types.SpecReport { return reportToReturn } + ginkgoRecoverFunc := func() { + recoverCall = true + } + + attachProgressReporterFunc := func(f func() string) func() { + attachedProgressReporter = f + return func() { + attachProgressReporterCancelCalled = true + } + } buf = gbytes.NewBuffer() - t = testingtproxy.New(buf, failFunc, skipFunc, DeferCleanup, reportFunc, offset) + t = testingtproxy.New( + internal.NewWriter(buf), + failFunc, + skipFunc, + DeferCleanup, + reportFunc, + AddReportEntry, + ginkgoRecoverFunc, + attachProgressReporterFunc, + 17, + 3, + 5, + true, + offset) }) Describe("Cleanup", Ordered, func() { @@ -179,12 +209,12 @@ var _ = Describe("Testingtproxy", func() { It("supports Log", func() { t.Log("a", 17) - Ω(string(buf.Contents())).Should(Equal("a 17\n")) + Ω(string(buf.Contents())).Should(Equal(" a 17\n")) }) It("supports Logf", func() { t.Logf("%s %d!", "a", 17) - Ω(string(buf.Contents())).Should(Equal("a 17!\n")) + Ω(string(buf.Contents())).Should(Equal(" a 17!\n")) }) It("supports Name", func() { @@ -222,4 +252,107 @@ var _ = Describe("Testingtproxy", func() { reportToReturn.State = types.SpecStateSkipped Ω(t.Skipped()).Should(BeTrue()) }) + + It("can add report entries with visibility Always", func() { + cl := types.NewCodeLocation(0) + t.AddReportEntryVisibilityAlways("hey", 3) + entry := CurrentSpecReport().ReportEntries[0] + Ω(entry.Visibility).Should(Equal(types.ReportEntryVisibilityAlways)) + Ω(entry.Name).Should(Equal("hey")) + Ω(entry.GetRawValue()).Should(Equal(3)) + Ω(entry.Location.FileName).Should(Equal(cl.FileName)) + Ω(entry.Location.LineNumber).Should(Equal(cl.LineNumber + 1)) + }) + + It("can add report entries with visibility FailureOrVerbose", func() { + cl := types.NewCodeLocation(0) + t.AddReportEntryVisibilityFailureOrVerbose("hey", 3) + entry := CurrentSpecReport().ReportEntries[0] + Ω(entry.Visibility).Should(Equal(types.ReportEntryVisibilityFailureOrVerbose)) + Ω(entry.Name).Should(Equal("hey")) + Ω(entry.GetRawValue()).Should(Equal(3)) + Ω(entry.Location.FileName).Should(Equal(cl.FileName)) + Ω(entry.Location.LineNumber).Should(Equal(cl.LineNumber + 1)) + }) + + It("can add report entries with visibility Never", func() { + cl := types.NewCodeLocation(0) + t.AddReportEntryVisibilityNever("hey", 3) + entry := CurrentSpecReport().ReportEntries[0] + Ω(entry.Visibility).Should(Equal(types.ReportEntryVisibilityNever)) + Ω(entry.Name).Should(Equal("hey")) + Ω(entry.GetRawValue()).Should(Equal(3)) + Ω(entry.Location.FileName).Should(Equal(cl.FileName)) + Ω(entry.Location.LineNumber).Should(Equal(cl.LineNumber + 1)) + }) + + It("can print to the GinkgoWriter", func() { + t.Print("hi", 3) + Ω(string(buf.Contents())).Should(Equal(" hi3")) + }) + + It("can printf to the GinkgoWriter", func() { + t.Printf("hi %d", 3) + Ω(string(buf.Contents())).Should(Equal(" hi 3")) + }) + + It("can println to the GinkgoWriter", func() { + t.Println("hi", 3) + Ω(string(buf.Contents())).Should(Equal(" hi 3\n")) + }) + + It("can provides a correctly configured Ginkgo Formatter", func() { + Ω(t.F("{{blue}}%d{{/}}", 3)).Should(Equal("3")) + }) + + It("can printf to the GinkgoWriter", func() { + Ω(t.Fi(1, "{{blue}}%d{{/}}", 3)).Should(Equal(" 3")) + }) + + It("can println to the GinkgoWriter", func() { + Ω(t.Fiw(1, 5, "{{blue}}%d{{/}} a number", 3)).Should(Equal(" 3 a\n number")) + }) + + It("can provide GinkgoRecover", func() { + Ω(recoverCall).Should(BeFalse()) + t.GinkgoRecover() + Ω(recoverCall).Should(BeTrue()) + }) + + Describe("DeferCleanup", Ordered, func() { + var a int + It("provides access to DeferCleanup", func() { + a = 3 + t.DeferCleanup(func(newA int) { + a = newA + }, 4) + }) + + It("provides access to DeferCleanup", func() { + Ω(a).Should(Equal(4)) + }) + }) + + It("provides the random seed", func() { + Ω(t.RandomSeed()).Should(Equal(int64(17))) + }) + + It("provides the parallel process", func() { + Ω(t.ParallelProcess()).Should(Equal(3)) + }) + + It("provides the parallel total", func() { + Ω(t.ParallelTotal()).Should(Equal(5)) + }) + + It("can attach progress reports", func() { + cancel := t.AttachProgressReporter(func() string { + return "my report" + }) + Ω(attachedProgressReporter()).Should(Equal("my report")) + Ω(attachProgressReporterCancelCalled).Should(BeFalse()) + cancel() + Ω(attachProgressReporterCancelCalled).Should(BeTrue()) + }) + })