Skip to content

Commit

Permalink
Introduce new DSL packages to enable users to pick-and-choose which p…
Browse files Browse the repository at this point in the history
…ortions of the DSL to dot-import.
  • Loading branch information
onsi committed Jan 12, 2022
1 parent 146654c commit 90868e2
Show file tree
Hide file tree
Showing 10 changed files with 364 additions and 71 deletions.
97 changes: 97 additions & 0 deletions docs/MIGRATING_TO_V2.md
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions docs/index.md
Expand Up @@ -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.
Expand Down
59 changes: 59 additions & 0 deletions 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
29 changes: 29 additions & 0 deletions 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
68 changes: 68 additions & 0 deletions 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))
})
30 changes: 30 additions & 0 deletions 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
31 changes: 31 additions & 0 deletions 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

0 comments on commit 90868e2

Please sign in to comment.