From 92b6744a3a0c4e7a196009a0620b6c211e1d8b8e Mon Sep 17 00:00:00 2001 From: Ergin Babani Date: Mon, 8 Jan 2024 19:51:31 -0500 Subject: [PATCH] Add GinkgoTB() function (#1333) This function returns a wrapper around GinkgoT() which satisfies the `testing.TB` interface. - Update fail tests with tests for GinkgoT() and GinkgoTB(). - Fix a bug where GinkgoT().Fail() was not reporting the correct line on error. --- docs/index.md | 2 + dsl/core/core_dsl.go | 2 + dsl/dsl_suite_test.go | 4 +- ginkgo_t_dsl.go | 106 ++++++++++++++++-- .../fail_fixture_ginkgo_t_test.go | 35 ++++++ .../fail_fixture_ginkgo_tb_test.go | 36 ++++++ .../more_ginkgo_tests_test.go | 10 ++ integration/fail_test.go | 26 ++++- integration/flags_test.go | 4 +- integration/run_test.go | 6 +- integration/verbose_and_succinct_test.go | 2 +- internal/testingtproxy/testingtproxy_test.go | 1 + 12 files changed, 214 insertions(+), 20 deletions(-) create mode 100644 integration/_fixtures/fail_fixture/fail_fixture_ginkgo_t_test.go create mode 100644 integration/_fixtures/fail_fixture/fail_fixture_ginkgo_tb_test.go diff --git a/docs/index.md b/docs/index.md index 9f09075fa..6aee23662 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5478,6 +5478,8 @@ When using Gomock you may want to run `ginkgo` with the `-trace` flag to print o `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. +Similarly for third party libraries which accept a `testing.TB` interface, use the `GinkgoTB()` function. This function returns a struct wrapper around `GinkgoT()` which satisfies the `testing.TB`interface. If you need to use any Ginkgo-specific methods you can access the wrapped `GinkgoT()` instance using `GinkgoTBWrapper.GinkgoT`. + ### 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 0796cee68..b2dad1bef 100644 --- a/dsl/core/core_dsl.go +++ b/dsl/core/core_dsl.go @@ -23,6 +23,7 @@ type GinkgoTestingT = ginkgo.GinkgoTestingT type GinkgoTInterface = ginkgo.GinkgoTInterface type FullGinkgoTInterface = ginkgo.FullGinkgoTInterface type SpecContext = ginkgo.SpecContext +type GinkgoTBWrapper = ginkgo.GinkgoTBWrapper var GinkgoWriter = ginkgo.GinkgoWriter var GinkgoLogr = ginkgo.GinkgoLogr @@ -63,4 +64,5 @@ var BeforeAll = ginkgo.BeforeAll var AfterAll = ginkgo.AfterAll var DeferCleanup = ginkgo.DeferCleanup var GinkgoT = ginkgo.GinkgoT +var GinkgoTB = ginkgo.GinkgoTB var AttachProgressReporter = ginkgo.AttachProgressReporter diff --git a/dsl/dsl_suite_test.go b/dsl/dsl_suite_test.go index 01d3c5e83..ce3ffeb10 100644 --- a/dsl/dsl_suite_test.go +++ b/dsl/dsl_suite_test.go @@ -21,7 +21,9 @@ func ExtractSymbols(f *ast.File) []string { names := []string{} switch v := decl.(type) { case *ast.FuncDecl: - names = append(names, v.Name.Name) + if v.Recv == nil { + names = append(names, v.Name.Name) + } case *ast.GenDecl: switch v.Tok { case token.TYPE: diff --git a/ginkgo_t_dsl.go b/ginkgo_t_dsl.go index 28447ffdd..e908cbfbf 100644 --- a/ginkgo_t_dsl.go +++ b/ginkgo_t_dsl.go @@ -1,7 +1,10 @@ package ginkgo import ( + "testing" + "github.com/onsi/ginkgo/v2/internal/testingtproxy" + "github.com/onsi/ginkgo/v2/types" ) /* @@ -15,7 +18,7 @@ correct line number associated with the failure - though you do not need to use 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 +44,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 +74,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 +95,82 @@ 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() +} diff --git a/integration/_fixtures/fail_fixture/fail_fixture_ginkgo_t_test.go b/integration/_fixtures/fail_fixture/fail_fixture_ginkgo_t_test.go new file mode 100644 index 000000000..5e161bbac --- /dev/null +++ b/integration/_fixtures/fail_fixture/fail_fixture_ginkgo_t_test.go @@ -0,0 +1,35 @@ +package fail_fixture_test + +import ( + . "github.com/onsi/ginkgo/v2" +) + +var _ = Describe("GinkgoT", func() { + It("synchronous failures with GinkgoT().Fail", func() { + GinkgoT().Fail() + println("NEVER SEE THIS") + }) + + DescribeTable("DescribeTable", + func() { + GinkgoT().Fail() + }, + Entry("a TableEntry constructed by Entry"), + ) + + It("tracks line numbers correctly when GinkgoT().Helper() is called", func() { + ginkgoTHelper() + }) + + It("tracks the actual line number when no helper is used", func() { + ginkgoTNoHelper() + }) +}) + +var ginkgoTNoHelper = func() { + GinkgoT().Fail() +} +var ginkgoTHelper = func() { + GinkgoT().Helper() + GinkgoT().Fail() +} diff --git a/integration/_fixtures/fail_fixture/fail_fixture_ginkgo_tb_test.go b/integration/_fixtures/fail_fixture/fail_fixture_ginkgo_tb_test.go new file mode 100644 index 000000000..c024adbc3 --- /dev/null +++ b/integration/_fixtures/fail_fixture/fail_fixture_ginkgo_tb_test.go @@ -0,0 +1,36 @@ +package fail_fixture_test + +import ( + . "github.com/onsi/ginkgo/v2" +) + +var _ = Describe("GinkgoTB", func() { + It("synchronous failures with GinkgoTB().Fail", func() { + GinkgoTB().Fail() + println("NEVER SEE THIS") + }) + + DescribeTable("DescribeTable", + func() { + GinkgoTB().Fail() + }, + Entry("a TableEntry constructed by Entry"), + ) + + It("tracks line numbers correctly when GinkgoTB().Helper() is called", func() { + ginkgoTBHelper() + }) + + It("tracks the actual line number when no GinkgoTB helper is used", func() { + ginkgoTBNoHelper() + }) +}) + +var ginkgoTBNoHelper = func() { + GinkgoTB().Fail() +} +var ginkgoTBHelper = func() { + t := GinkgoTB() + t.Helper() + t.Fail() +} diff --git a/integration/_fixtures/more_ginkgo_tests_fixture/more_ginkgo_tests_test.go b/integration/_fixtures/more_ginkgo_tests_fixture/more_ginkgo_tests_test.go index c5da1d496..2c13271a3 100644 --- a/integration/_fixtures/more_ginkgo_tests_fixture/more_ginkgo_tests_test.go +++ b/integration/_fixtures/more_ginkgo_tests_fixture/more_ginkgo_tests_test.go @@ -1,6 +1,8 @@ package more_ginkgo_tests_test import ( + "testing" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/integration/_fixtures/more_ginkgo_tests_fixture" . "github.com/onsi/gomega" @@ -14,4 +16,12 @@ var _ = Describe("MoreGinkgoTests", func() { It("should always pass", func() { Ω(AlwaysTrue()).Should(BeTrue()) }) + + It("should match testing.TB", func() { + var tbFunc = func(_ testing.TB) { + Ω(AlwaysTrue()).Should(BeTrue()) + } + + tbFunc(GinkgoTB()) + }) }) diff --git a/integration/fail_test.go b/integration/fail_test.go index 4bea55a6d..ca4b2b0d9 100644 --- a/integration/fail_test.go +++ b/integration/fail_test.go @@ -41,7 +41,31 @@ var _ = Describe("Failing Specs", func() { Ω(output).Should(ContainSubstring(`a helper failed`)) Ω(output).Should(ContainSubstring(`fail_fixture_test.go:54`), "the code location reported for the helper failure - we're testing the call to GinkgoHelper() works as expected") - Ω(output).Should(ContainSubstring("0 Passed | 8 Failed")) + Ω(output).Should(ContainSubstring("synchronous failures with GinkgoT().Fail")) + Ω(output).Should(ContainSubstring("fail_fixture_ginkgo_t_test.go:9")) + + Ω(output).Should(ContainSubstring("GinkgoT DescribeTable")) + Ω(output).Should(ContainSubstring("fail_fixture_ginkgo_t_test.go:15")) + + Ω(output).Should(ContainSubstring(`tracks line numbers correctly when GinkgoT().Helper() is called`)) + Ω(output).Should(ContainSubstring(`fail_fixture_ginkgo_t_test.go:21`), "the code location reported for the ginkgoT helper failure") + + Ω(output).Should(ContainSubstring(`tracks the actual line number when no helper is used`)) + Ω(output).Should(ContainSubstring(`fail_fixture_ginkgo_t_test.go:30`), "the code location reported for the ginkgoT no helper failure") + + Ω(output).Should(ContainSubstring("synchronous failures with GinkgoTB().Fail")) + Ω(output).Should(ContainSubstring("fail_fixture_ginkgo_tb_test.go:9")) + + Ω(output).Should(ContainSubstring("GinkgoTB DescribeTable")) + Ω(output).Should(ContainSubstring("fail_fixture_ginkgo_tb_test.go:15")) + + Ω(output).Should(ContainSubstring(`tracks line numbers correctly when GinkgoTB().Helper() is called`)) + Ω(output).Should(ContainSubstring(`fail_fixture_ginkgo_tb_test.go:21`), "the code location reported for the ginkgoTB helper failure") + + Ω(output).Should(ContainSubstring(`tracks the actual line number when no GinkgoTB helper is used`)) + Ω(output).Should(ContainSubstring(`fail_fixture_ginkgo_tb_test.go:30`), "the code location reported for the ginkgoT no helper failure") + + Ω(output).Should(ContainSubstring("0 Passed | 16 Failed")) }) }) diff --git a/integration/flags_test.go b/integration/flags_test.go index 72d48c099..18fcdea9b 100644 --- a/integration/flags_test.go +++ b/integration/flags_test.go @@ -129,8 +129,8 @@ var _ = Describe("Flags Specs", func() { output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("synchronous failures")) - Ω(output).Should(ContainSubstring("8 Specs")) - Ω(output).Should(ContainSubstring("8 Passed")) + Ω(output).Should(ContainSubstring("16 Specs")) + Ω(output).Should(ContainSubstring("16 Passed")) Ω(output).Should(ContainSubstring("0 Failed")) }) diff --git a/integration/run_test.go b/integration/run_test.go index 6297ce5fd..e59c2384f 100644 --- a/integration/run_test.go +++ b/integration/run_test.go @@ -339,7 +339,7 @@ var _ = Describe("Running Specs", func() { output := string(session.Out.Contents()) outputLines := strings.Split(output, "\n") - Ω(outputLines[0]).Should(MatchRegexp(`\[\d+\] More_ginkgo_tests Suite - 2/2 specs [%s]{2} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) + Ω(outputLines[0]).Should(MatchRegexp(`\[\d+\] More_ginkgo_tests Suite - 3/3 specs [%s]{3} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) Ω(outputLines[1]).Should(ContainSubstring("Skipping ./no_tagged_tests (no test files)")) Ω(outputLines[2]).Should(MatchRegexp(`\[\d+\] Passing_ginkgo_tests Suite - 5/5 specs [%s]{5} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) Ω(output).Should(ContainSubstring("Test Suite Passed")) @@ -352,7 +352,7 @@ var _ = Describe("Running Specs", func() { output := string(session.Out.Contents()) outputLines := strings.Split(output, "\n") - Ω(outputLines[0]).Should(MatchRegexp(`\[\d+\] More_ginkgo_tests Suite - 2/2 specs [%s]{2} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) + Ω(outputLines[0]).Should(MatchRegexp(`\[\d+\] More_ginkgo_tests Suite - 3/3 specs [%s]{3} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) Ω(outputLines[1]).Should(ContainSubstring("Skipping ./no_tagged_tests (no test files)")) Ω(outputLines[2]).Should(MatchRegexp(`\[\d+\] Passing_ginkgo_tests Suite - 5/5 specs [%s]{5} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) Ω(output).Should(ContainSubstring("Test Suite Passed")) @@ -416,7 +416,7 @@ var _ = Describe("Running Specs", func() { Ω(outputLines[2]).Should(ContainSubstring("Failed to compile does_not_compile:")) Ω(output).Should(MatchRegexp(`\[\d+\] Failing_ginkgo_tests Suite - 2/2 specs`)) Ω(output).Should(ContainSubstring(fmt.Sprintf("%s [FAILED]", denoter))) - Ω(output).Should(MatchRegexp(`\[\d+\] More_ginkgo_tests Suite - 2/2 specs [%s]{2} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) + Ω(output).Should(MatchRegexp(`\[\d+\] More_ginkgo_tests Suite - 3/3 specs [%s]{3} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) Ω(output).Should(ContainSubstring("Test Suite Failed")) }) }) diff --git a/integration/verbose_and_succinct_test.go b/integration/verbose_and_succinct_test.go index eb1087622..8720f245a 100644 --- a/integration/verbose_and_succinct_test.go +++ b/integration/verbose_and_succinct_test.go @@ -43,7 +43,7 @@ var _ = Describe("Verbose And Succinct Mode", func() { output := session.Out.Contents() Ω(output).Should(MatchRegexp(`\] Passing_ginkgo_tests Suite - 5/5 specs [%s]{5} SUCCESS!`, regexp.QuoteMeta(denoter))) - Ω(output).Should(MatchRegexp(`\] More_ginkgo_tests Suite - 2/2 specs [%s]{2} SUCCESS!`, regexp.QuoteMeta(denoter))) + Ω(output).Should(MatchRegexp(`\] More_ginkgo_tests Suite - 3/3 specs [%s]{3} SUCCESS!`, regexp.QuoteMeta(denoter))) }) }) diff --git a/internal/testingtproxy/testingtproxy_test.go b/internal/testingtproxy/testingtproxy_test.go index 22a3e9242..518e3a8eb 100644 --- a/internal/testingtproxy/testingtproxy_test.go +++ b/internal/testingtproxy/testingtproxy_test.go @@ -223,6 +223,7 @@ var _ = Describe("Testingtproxy", func() { reportToReturn.LeafNodeText = "Lewis" Ω(t.Name()).Should(Equal("C.S. Lewis")) Ω(GinkgoT().Name()).Should(ContainSubstring("supports Name")) + Ω(GinkgoTB().Name()).Should(ContainSubstring("supports Name")) }) It("ignores Parallel", func() {