From bf78c28ed3cb46845103d7f3c4279b7ced799dc1 Mon Sep 17 00:00:00 2001 From: Erik Schubert Date: Sun, 23 Oct 2022 14:26:33 +0200 Subject: [PATCH] Add GinkgoLogr (#1067) --- core_dsl.go | 17 ++++++++++++----- docs/index.md | 2 ++ dsl/core/core_dsl.go | 3 ++- go.mod | 1 + go.sum | 2 ++ internal/writer.go | 9 +++++++++ internal/writer_test.go | 16 ++++++++++++++++ 7 files changed, 44 insertions(+), 6 deletions(-) diff --git a/core_dsl.go b/core_dsl.go index aaff7800d..6e20042c9 100644 --- a/core_dsl.go +++ b/core_dsl.go @@ -23,6 +23,7 @@ import ( "strings" "time" + "github.com/go-logr/logr" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/internal/global" @@ -46,7 +47,9 @@ func init() { var err error flagSet, err = types.BuildTestSuiteFlagSet(&suiteConfig, &reporterConfig) exitIfErr(err) - GinkgoWriter = internal.NewWriter(os.Stdout) + writer := internal.NewWriter(os.Stdout) + GinkgoWriter = writer + GinkgoLogr = internal.GinkgoLogrFunc(writer) } func exitIfErr(err error) { @@ -90,11 +93,11 @@ type GinkgoWriterInterface interface { } /* - SpecContext is the context object passed into nodes that are subject to a timeout or need to be notified of an interrupt. It implements the standard context.Context interface but also contains additional helpers to provide an extensibility point for Ginkgo. (As an example, Gomega's Eventually can use the methods defined on SpecContext to provide deeper integratoin with Ginkgo). +SpecContext is the context object passed into nodes that are subject to a timeout or need to be notified of an interrupt. It implements the standard context.Context interface but also contains additional helpers to provide an extensibility point for Ginkgo. (As an example, Gomega's Eventually can use the methods defined on SpecContext to provide deeper integratoin with Ginkgo). - You can do anything with SpecContext that you do with a typical context.Context including wrapping it with any of the context.With* methods. +You can do anything with SpecContext that you do with a typical context.Context including wrapping it with any of the context.With* methods. - Ginkgo will cancel the SpecContext when a node is interrupted (e.g. by the user sending an interupt signal) or when a node has exceeded it's allowed run-time. Note, however, that even in cases where a node has a deadline, SpecContext will not return a deadline via .Deadline(). This is because Ginkgo does not use a WithDeadline() context to model node deadlines as Ginkgo needs control over the precise timing of the context cancellation to ensure it can provide an accurate progress report at the moment of cancellation. +Ginkgo will cancel the SpecContext when a node is interrupted (e.g. by the user sending an interupt signal) or when a node has exceeded it's allowed run-time. Note, however, that even in cases where a node has a deadline, SpecContext will not return a deadline via .Deadline(). This is because Ginkgo does not use a WithDeadline() context to model node deadlines as Ginkgo needs control over the precise timing of the context cancellation to ensure it can provide an accurate progress report at the moment of cancellation. */ type SpecContext = internal.SpecContext @@ -112,6 +115,11 @@ You can learn more at https://onsi.github.io/ginkgo/#logging-output */ var GinkgoWriter GinkgoWriterInterface +/* +GinkgoLogr is a logr.Logger that writes to GinkgoWriter +*/ +var GinkgoLogr logr.Logger + // The interface by which Ginkgo receives *testing.T type GinkgoTestingT interface { Fail() @@ -686,7 +694,6 @@ Multiple BeforeAll nodes can be defined in a given Ordered container however the BeforeAll can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. - You cannot nest any other Ginkgo nodes within a BeforeAll node's closure. You can learn more about Ordered Containers at: https://onsi.github.io/ginkgo/#ordered-containers And you can learn more about BeforeAll at: https://onsi.github.io/ginkgo/#setup-in-ordered-containers-beforeall-and-afterall diff --git a/docs/index.md b/docs/index.md index 64b447a81..07163cc97 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1183,6 +1183,8 @@ You can also attach additional `io.Writer`s for `GinkgoWriter` to tee to via `Gi Finally - when running in verbose mode via `ginkgo -v` anything written to `GinkgoWriter` will be immediately streamed to stdout. This can help shorten the feedback loop when debugging a complex spec. +If [logr](https://github.com/go-logr/logr) is used for logging in a project the globally available `GinkgoLogr` provides a logger implementation. Any logging on `GinkgoLogr` is forwarded to `GinkgoWriter`. + ### Documenting Complex Specs: By As a rule, you should try to keep your subject and setup closures short and to the point. Sometimes this is not possible, particularly when testing complex workflows in integration-style tests. In these cases your test blocks begin to hide a narrative that is hard to glean by looking at code alone. Ginkgo provides `By` to help in these situations. Here's an example: diff --git a/dsl/core/core_dsl.go b/dsl/core/core_dsl.go index 7a8d0fd36..e62d2a1c9 100644 --- a/dsl/core/core_dsl.go +++ b/dsl/core/core_dsl.go @@ -1,7 +1,7 @@ /* Ginkgo is usually dot-imported via: - import . "github.com/onsi/ginkgo/v2" + import . "github.com/onsi/ginkgo/v2" however some parts of the DSL may conflict with existing symbols in the user's code. @@ -24,6 +24,7 @@ type GinkgoTInterface = ginkgo.GinkgoTInterface type SpecContext = ginkgo.SpecContext var GinkgoWriter = ginkgo.GinkgoWriter +var GinkgoLogr = ginkgo.GinkgoLogr var GinkgoConfiguration = ginkgo.GinkgoConfiguration var GinkgoRandomSeed = ginkgo.GinkgoRandomSeed var GinkgoParallelProcess = ginkgo.GinkgoParallelProcess diff --git a/go.mod b/go.mod index 997ff5e8b..c53ecdc95 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/onsi/ginkgo/v2 go 1.18 require ( + github.com/go-logr/logr v1.2.3 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 github.com/onsi/gomega v1.22.1 diff --git a/go.sum b/go.sum index cbee4b333..327596af8 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= diff --git a/internal/writer.go b/internal/writer.go index 70f0a41f6..da21d3b06 100644 --- a/internal/writer.go +++ b/internal/writer.go @@ -5,6 +5,9 @@ import ( "fmt" "io" "sync" + + "github.com/go-logr/logr" + "github.com/go-logr/logr/funcr" ) type WriterMode uint @@ -101,3 +104,9 @@ func (w *Writer) Printf(format string, a ...interface{}) { func (w *Writer) Println(a ...interface{}) { fmt.Fprintln(w, a...) } + +func GinkgoLogrFunc(writer *Writer) logr.Logger { + return funcr.New(func(prefix, args string) { + writer.Printf("%s", args) + }, funcr.Options{}) +} diff --git a/internal/writer_test.go b/internal/writer_test.go index 284a2fd05..a9842b65a 100644 --- a/internal/writer_test.go +++ b/internal/writer_test.go @@ -1,6 +1,8 @@ package internal_test import ( + "errors" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -108,4 +110,18 @@ var _ = Describe("Writer", func() { Ω(string(out.Contents())).Should(Equal("foo17 - bar\n")) }) }) + + When("wrapped by logr", func() { + It("can print Info logs", func() { + log := internal.GinkgoLogrFunc(writer) + log.Info("message", "key", 5) + Ω(string(out.Contents())).Should(Equal("\"level\"=0 \"msg\"=\"message\" \"key\"=5")) + }) + + It("can print Error logs", func() { + log := internal.GinkgoLogrFunc(writer) + log.Error(errors.New("cake"), "planned failure", "key", "banana") + Ω(string(out.Contents())).Should(Equal("\"msg\"=\"planned failure\" \"error\"=\"cake\" \"key\"=\"banana\"")) + }) + }) })