diff --git a/docs/MIGRATING_TO_V2.md b/docs/MIGRATING_TO_V2.md index 84b5d4fbb..8956f8045 100644 --- a/docs/MIGRATING_TO_V2.md +++ b/docs/MIGRATING_TO_V2.md @@ -33,6 +33,8 @@ Updating to V2 will require you to make some changes to your test suites however With the release of Ginkgo 2.0 the 1.x version is formally deprecated and no longer supported. All future development will occur on version 2. +The next sections describe the [new features in Ginkgo 2.0](#major-additions-and-improvements) and the [major changes](#major-changes) alogn with details on how to migrate your test code to adapt to the changes. At the end of this doc is an [FAQ](#faq) with common gotchas that will be tracked as they emerge. + ## Major Additions and Improvements ### Interrupt Behavior @@ -583,6 +585,101 @@ These are minor changes that will be transparent for most users. - Removed `ginkgo blur` alias. Use `ginkgo unfocus` instead. +## FAQ + +As users have started adopting Ginkgo v2 they've bumped into a few specific issues. This FAQ will grow as these issues are identified to help address them. + +### Can I mix Ginkgo V1 and Ginkgo V2? + +..._ish_. + +**What you _can't_ do** + +Under the hood Ginkgo V2 is effectively a rewrite of Ginkgo V1. While the external interfaces are largely compatible (modulo the differences pointed out in this doc) the internals are very different. Because of this **it is not possible** to import and use V1 _and_ V2 **in the same _package_**. + +In fact, trying to do so will result in a crash as Ginkgo V1's `init` function and Ginkgo V2's `init` function will register conflicting command line flags. + +That means you can't do something like: + +```go +/* sprockets/widget_test.go */ + +import ( + . "github.com/onsi/ginkgo" //v1 +) + +var _ = It("uses V1", func() {...}) + +/* sprockets/doodad_test.go */ + +import ( + . "github.com/onsi/ginkgo/v2" //v2 +) + +var _ = It("uses V2", func() {...}) +``` + +It _also_ means you can't use a _dependency_ in your test that, in turn, imports a mismatched version of Ginkgo. For example, let's say we have a test helper package: + +```go +/* helpers/test_helper.go */ + +import ( + "github.com/onsi/ginkgo" //imports v1 +) + +func EnsureNoSprocketRust(sprocket *Sprocket) { + if sprocket.IsRusty() { + Fail("Sprocket rust detected") + } +} +``` + +this test helper package imports Ginkgo V1. If we try to use it in a test package that uses Ginkgo V2: + + +```go +/* sprockets/widget_test.go */ + +import ( + . "github.com/onsi/ginkgo/v2" //v2 + "helpers" //imports v1 => boom +) + +var _ = It("has no rusty sprockets", func() { + helpers.EnsureNoSprocketRust(sprocket) +}) +``` + +this won't work as the two versions of Ginkgo will be imported and result in a conflict. + +Lastly, you can run into this issue accidentally while upgrading to 2.0 if you update some, but not all, of the import statements in your package. + +**What you _can_ do** + +While you cannot import V1 and V2 in the same package you _can_ have some packages that use V1 and other packages that use V2 associated with a given module. The different test packages are compiled separately and the V1 packages will use Ginkgo V1 whereas the V2 packages will use Ginkgo V2. Go basically treats different major versions of a dependency as completely different packages. + +This means that your dependencies can use a different major version of Ginkgo for _their_ test suites than your codebase (as long as you aren't importing a test-helper dependency into your test suite and running into the major version clash described above). + +This _also_ means that you can, in principle, upgrade different test suites in your module at different times. For example, in a fictitious `factory` module the `sprockets` package can be upgraded to Ginkgo V2 first, and the `convery_belt` package can stay at Ginkgo V1 until later. In _practice_ however, you'll run into difficulties as the `ginkgo` cli used to invoke the tests will be at a different major version than some subset of packages under test - this basically won't work because of changes in the client/server contract between the CLI and the test library across the two major versions. So you'll need to take care to use the correct version of the cli with the correct test package. In general the migration to V2 is intended to be simple enough that you should rarely need to resort to having mixed-version numbers like this. + +### A symbol in V2 now clashes with a symbol in my codebase. What do I do? +If Ginkgo 2.0 introduces a new exported symbl that now clashes with your codebase (because you are dot-importing Ginkgo). Check out the [Alternatives to Dot-Importing Ginkgo](https://onsi.github.io/ginkgo/#alternatives-to-dot-importing-ginkgo) section of the documentation for some options. You may be able to, instead, dot-import just a subset of the Ginkgo DSL using the new `github.com/onsi/ginkgo/v2/dsl` set of packages. + +Specificaly when upgrading from v1 to v2 if you see a dot-import clash due to a newly introduced symbol (e.g. the new `Label` decorator) you can instead choose to dot-import the core DSL and import the `decorator` dsl separately: + +```go +import ( + . "github.com/onsi/ginkgo/v2/dsl/core" + "github.com/onsi/ginkgo/v2/dsl/decorators" +) + +var _ = It("gives you the core DSL", decorators.Label("and namespaced decorators"), func() { + ... +}) + +``` + --- # Using the Beta diff --git a/docs/index.md b/docs/index.md index f9edc0732..920f08c11 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1510,6 +1510,56 @@ var _ = Describe("Math", func() { Will generate entries named: `1 + 2 = 3`, `-1 + 2 = 1`, `zeros`, `110 = 10 + 100`, and `7 = 7`. +### Alternatives to Dot-Importing Ginkgo + +As shown througout this documentation, Ginkgo users are encouraged to dot-import the Ginkgo DSL into their test suites to effectively extend the Go language with Ginkgo's expressive building blocks: + +```go +import . "github.com/onsi/ginkgo/v2" +``` + +Some users prefer to avoid dot-importing dependencies into their code in order to keep their global namespace clean and predictable. You can, of course, do this with Ginkgo - we recommend using a simple shorthand like `g`: + +```go +import g "github.com/onsi/ginkgo/v2" +``` + +now you can write tests as before, albeit with a slight stutter: + +```go +var _ = g.Describe("Books", func() { + g.BeforeEach(func() { ... }) + + g.It("works as before", func() { + g.By("you just need to repeat g. everywhere") + }) +}) +``` + +Alternatively, you can choose to dot-import only _portions_ of Ginkgo's DSL into the global namespace. The packages under `github.com/onsi/ginkgo/v2/dsl` organize the various pieces of Ginkgo into a series of subpackages. You can choose to mix-and-match which of these are dot-imported vs namespaced. For example, you can dot-import the core DSL (which provides the various setup, container, and subject nodes) while namespace importing the decorators DSL: + +```go +import ( + . "github.com/onsi/ginkgo/v2/dsl/core" + "github.com/onsi/ginkgo/v2/dsl/decorators" +) + +var _ = It("gives you the core DSL", decorators.Label("and namespaced decorators"), func() { + ... +}) +``` + +The available DSL packages are: + +| Package | Contents | +|-------|--------| +| `github.com/onsi/ginkgo/v2/dsl/core` | The core DSL including all container, setup, and subject nodes (`Describe`, `Context`, `BeforeEach`, `BeforeSuite`, `It`, etc...) as well as the most commonly used functions (`RunSpecs`, `Skip`, `Fail`, `By`, `GinkgoT`) | +| `github.com/onsi/ginkgo/v2/decorators` | The decorator DSL includes all Ginkgo's decorators (e.g. `Label`, `Ordered`, `Serial`, etc...) | +| `github.com/onsi/ginkgo/v2/reporting` | The reporting DSL includes all reporting-related nodes and types (e.g. `Report`, `CurrentSpecReport`, `ReportAfterEach`, `AddReportEntry`) | +| `github.com/onsi/ginkgo/v2/table` | The table DSL includes all table-related types and functions (e.g. `DescribeTable`, `Entry`, `EntryDescription`) | + +The DSL packages simply import and then re-export pieces of the Ginkgo DSL provided by `github.com/onsi/ginkgo/v2` so there are no differences in behavior or interoperability if you use the standard dot-import for Ginkgo or pull in the various DSL packages in piecemeal. + ## Running Specs The previous chapter covered the basics of [Writing Specs](#writing-specs) in Ginkgo. We explored how Ginkgo lets you use container nodes, subject nodes, and setup nodes to construct hierarchical spec trees; and how Ginkgo transforms those trees into a list of specs to run. diff --git a/dsl/core/core_dsl.go b/dsl/core/core_dsl.go new file mode 100644 index 000000000..fac6f42d8 --- /dev/null +++ b/dsl/core/core_dsl.go @@ -0,0 +1,59 @@ +/* +Ginkgo isusually dot-imported via: + + import . "github.com/onsi/ginkgo/v2" + +however some parts of the DSL may conflict with existing symbols in the user's code. + +To mitigate this without losing the brevity of dot-importing Ginkgo the various packages in the +dsl directory provide pieces of the Ginkgo DSL that can be dot-imported separately. + +This "core" package pulls in the core Ginkgo DSL - most test suites will only need to import this package. +*/ +package core + +import ( + "github.com/onsi/ginkgo/v2" +) + +const GINKGO_VERSION = ginkgo.GINKGO_VERSION + +type GinkgoWriterInterface = ginkgo.GinkgoWriterInterface +type GinkgoTestingT = ginkgo.GinkgoTestingT +type GinkgoTInterface = ginkgo.GinkgoTInterface + +var GinkgoWriter = ginkgo.GinkgoWriter +var GinkgoConfiguration = ginkgo.GinkgoConfiguration +var GinkgoRandomSeed = ginkgo.GinkgoRandomSeed +var GinkgoParallelProcess = ginkgo.GinkgoParallelProcess +var PauseOutputInterception = ginkgo.PauseOutputInterception +var ResumeOutputInterception = ginkgo.ResumeOutputInterception +var RunSpecs = ginkgo.RunSpecs +var Skip = ginkgo.Skip +var Fail = ginkgo.Fail +var AbortSuite = ginkgo.AbortSuite +var GinkgoRecover = ginkgo.GinkgoRecover +var Describe = ginkgo.Describe +var FDescribe = ginkgo.FDescribe +var PDescribe = ginkgo.PDescribe +var XDescribe = PDescribe +var Context, FContext, PContext, XContext = Describe, FDescribe, PDescribe, XDescribe +var When, FWhen, PWhen, XWhen = Describe, FDescribe, PDescribe, XDescribe +var It = ginkgo.It +var FIt = ginkgo.FIt +var PIt = ginkgo.PIt +var XIt = PIt +var Specify, FSpecify, PSpecify, XSpecify = It, FIt, PIt, XIt +var By = ginkgo.By +var BeforeSuite = ginkgo.BeforeSuite +var AfterSuite = ginkgo.AfterSuite +var SynchronizedBeforeSuite = ginkgo.SynchronizedBeforeSuite +var SynchronizedAfterSuite = ginkgo.SynchronizedAfterSuite +var BeforeEach = ginkgo.BeforeEach +var JustBeforeEach = ginkgo.JustBeforeEach +var AfterEach = ginkgo.AfterEach +var JustAfterEach = ginkgo.JustAfterEach +var BeforeAll = ginkgo.BeforeAll +var AfterAll = ginkgo.AfterAll +var DeferCleanup = ginkgo.DeferCleanup +var GinkgoT = ginkgo.GinkgoT diff --git a/dsl/decorators/decorators_dsl.go b/dsl/decorators/decorators_dsl.go new file mode 100644 index 000000000..c41a49c65 --- /dev/null +++ b/dsl/decorators/decorators_dsl.go @@ -0,0 +1,29 @@ +/* +Ginkgo isusually dot-imported via: + + import . "github.com/onsi/ginkgo/v2" + +however some parts of the DSL may conflict with existing symbols in the user's code. + +To mitigate this without losing the brevity of dot-importing Ginkgo the various packages in the +dsl directory provide pieces of the Ginkgo DSL that can be dot-imported separately. + +This "decorators" package pulls in the various decorators defined in the Ginkgo DSL. +*/ +package decorators + +import ( + "github.com/onsi/ginkgo/v2" +) + +type Offset = ginkgo.Offset +type FlakeAttempts = ginkgo.FlakeAttempts +type Labels = ginkgo.Labels + +const Focus = ginkgo.Focus +const Pending = ginkgo.Pending +const Serial = ginkgo.Serial +const Ordered = ginkgo.Ordered +const OncePerOrdered = ginkgo.OncePerOrdered + +var Label = ginkgo.Label diff --git a/dsl/dsl_suite_test.go b/dsl/dsl_suite_test.go new file mode 100644 index 000000000..01d3c5e83 --- /dev/null +++ b/dsl/dsl_suite_test.go @@ -0,0 +1,68 @@ +package dsl_test + +import ( + "go/ast" + "go/parser" + "go/token" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestDSL(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "DSL Suite") +} + +func ExtractSymbols(f *ast.File) []string { + symbols := []string{} + for _, decl := range f.Decls { + names := []string{} + switch v := decl.(type) { + case *ast.FuncDecl: + names = append(names, v.Name.Name) + case *ast.GenDecl: + switch v.Tok { + case token.TYPE: + s := v.Specs[0].(*ast.TypeSpec) + names = append(names, s.Name.Name) + case token.CONST, token.VAR: + s := v.Specs[0].(*ast.ValueSpec) + for _, n := range s.Names { + names = append(names, n.Name) + } + } + } + for _, name := range names { + if ast.IsExported(name) { + symbols = append(symbols, name) + } + } + } + return symbols +} + +var _ = It("ensures complete coverage of the core dsl", func() { + fset := token.NewFileSet() + pkgs, err := parser.ParseDir(fset, "../", nil, 0) + Ω(err).ShouldNot(HaveOccurred()) + expectedSymbols := []string{} + for fn, file := range pkgs["ginkgo"].Files { + if fn == "../deprecated_dsl.go" { + continue + } + expectedSymbols = append(expectedSymbols, ExtractSymbols(file)...) + } + + actualSymbols := []string{} + for _, pkg := range []string{"core", "reporting", "decorators", "table"} { + pkgs, err := parser.ParseDir(fset, "./"+pkg, nil, 0) + Ω(err).ShouldNot(HaveOccurred()) + for _, file := range pkgs[pkg].Files { + actualSymbols = append(actualSymbols, ExtractSymbols(file)...) + } + } + + Ω(actualSymbols).Should(ConsistOf(expectedSymbols)) +}) diff --git a/dsl/reporting/reporting_dsl.go b/dsl/reporting/reporting_dsl.go new file mode 100644 index 000000000..86e3bd445 --- /dev/null +++ b/dsl/reporting/reporting_dsl.go @@ -0,0 +1,30 @@ +/* +Ginkgo isusually dot-imported via: + + import . "github.com/onsi/ginkgo/v2" + +however some parts of the DSL may conflict with existing symbols in the user's code. + +To mitigate this without losing the brevity of dot-importing Ginkgo the various packages in the +dsl directory provide pieces of the Ginkgo DSL that can be dot-imported separately. + +This "reporting" package pulls in the reporting-related pieces of the Ginkgo DSL. +*/ +package reporting + +import ( + "github.com/onsi/ginkgo/v2" +) + +type Report = ginkgo.Report +type SpecReport = ginkgo.SpecReport +type ReportEntryVisibility = ginkgo.ReportEntryVisibility + +const ReportEntryVisibilityAlways, ReportEntryVisibilityFailureOrVerbose, ReportEntryVisibilityNever = ginkgo.ReportEntryVisibilityAlways, ginkgo.ReportEntryVisibilityFailureOrVerbose, ginkgo.ReportEntryVisibilityNever + +var CurrentSpecReport = ginkgo.CurrentSpecReport +var AddReportEntry = ginkgo.AddReportEntry + +var ReportBeforeEach = ginkgo.ReportBeforeEach +var ReportAfterEach = ginkgo.ReportAfterEach +var ReportAfterSuite = ginkgo.ReportAfterSuite diff --git a/dsl/table/table_dsl.go b/dsl/table/table_dsl.go new file mode 100644 index 000000000..cf7c8bd3e --- /dev/null +++ b/dsl/table/table_dsl.go @@ -0,0 +1,31 @@ +/* +Ginkgo isusually dot-imported via: + + import . "github.com/onsi/ginkgo/v2" + +however some parts of the DSL may conflict with existing symbols in the user's code. + +To mitigate this without losing the brevity of dot-importing Ginkgo the various packages in the +dsl directory provide pieces of the Ginkgo DSL that can be dot-imported separately. + +This "table" package pulls in the Ginkgo's table-testing DSL +*/ +package table + +import ( + "github.com/onsi/ginkgo/v2" +) + +type EntryDescription = ginkgo.EntryDescription + +var DescribeTable = ginkgo.DescribeTable +var FDescribeTable = ginkgo.FDescribeTable +var PDescribeTable = ginkgo.PDescribeTable +var XDescribeTable = ginkgo.XDescribeTable + +type TableEntry = ginkgo.TableEntry + +var Entry = ginkgo.Entry +var FEntry = ginkgo.FEntry +var PEntry = ginkgo.PEntry +var XEntry = ginkgo.XEntry diff --git a/example/books/book_test.go b/example/books/book_test.go deleted file mode 100644 index 89cfc183a..000000000 --- a/example/books/book_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package books_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/ginkgo/v2/example/books" - . "github.com/onsi/gomega" -) - -var _ = Describe("Book", func() { - var ( - longBook Book - shortBook Book - ) - - BeforeEach(func() { - longBook = Book{ - Title: "Les Miserables", - Author: "Victor Hugo", - Pages: 2783, - } - - shortBook = Book{ - Title: "Fox In Socks", - Author: "Dr. Seuss", - Pages: 24, - } - }) - - Describe("Categorizing book length", func() { - Context("With more than 300 pages", func() { - It("should be a novel", func() { - Expect(longBook.CategoryByLength()).To(Equal("NOVEL")) - }) - }) - - Context("With fewer than 300 pages", func() { - It("should be a short story", func() { - Expect(shortBook.CategoryByLength()).To(Equal("SHORT STORY")) - }) - }) - }) -}) diff --git a/example/books/books.go b/example/books/books.go deleted file mode 100644 index ea7bb43f3..000000000 --- a/example/books/books.go +++ /dev/null @@ -1,16 +0,0 @@ -package books - -type Book struct { - Title string - Author string - Pages int -} - -func (b *Book) CategoryByLength() string { - - if b.Pages >= 300 { - return "NOVEL" - } - - return "SHORT STORY" -} diff --git a/example/books/books_suite_test.go b/example/books/books_suite_test.go deleted file mode 100644 index 541a7f139..000000000 --- a/example/books/books_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package books_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestBooks(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Books Suite") -}