Skip to content

Commit

Permalink
Preview now works alongside run and in parallel
Browse files Browse the repository at this point in the history
  • Loading branch information
onsi committed Oct 5, 2023
1 parent e1d0b38 commit 6c84b35
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 41 deletions.
29 changes: 17 additions & 12 deletions core_dsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ var flagSet types.GinkgoFlagSet
var deprecationTracker = types.NewDeprecationTracker()
var suiteConfig = types.NewDefaultSuiteConfig()
var reporterConfig = types.NewDefaultReporterConfig()
var suiteDidRun, suiteDidPreview = false, false
var suiteDidRun = false
var outputInterceptor internal.OutputInterceptor
var client parallel_support.Client

Expand Down Expand Up @@ -247,10 +247,12 @@ func RunSpecs(t GinkgoTestingT, description string, args ...interface{}) bool {
if suiteDidRun {
exitIfErr(types.GinkgoErrors.RerunningSuite())
}
if suiteDidPreview {
exitIfErr(types.GinkgoErrors.RunAndPreviewSuite())
}
suiteDidRun = true
err := global.PushClone()
if err != nil {
exitIfErr(err)
}
defer global.PopClone()

suiteLabels := extractSuiteConfiguration(args)

Expand Down Expand Up @@ -288,7 +290,7 @@ func RunSpecs(t GinkgoTestingT, description string, args ...interface{}) bool {
registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig)
}

err := global.Suite.BuildTree()
err = global.Suite.BuildTree()
exitIfErr(err)
suitePath, err := os.Getwd()
exitIfErr(err)
Expand Down Expand Up @@ -348,21 +350,24 @@ PreviewSpecs walks the testing tree and produces a report without actually invok
See http://onsi.github.io/ginkgo/#previewing-specs for more information.
*/
func PreviewSpecs(description string, args ...any) Report {
if suiteDidRun {
exitIfErr(types.GinkgoErrors.RunAndPreviewSuite())
err := global.PushClone()
if err != nil {
exitIfErr(err)
}
defer global.PopClone()

suiteLabels := extractSuiteConfiguration(args)
if suiteConfig.ParallelTotal != 1 {
exitIfErr(types.GinkgoErrors.PreviewInParallelConfiguration())
}
suiteConfig.DryRun = true
priorDryRun, priorParallelTotal, priorParallelProcess := suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess
suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess = true, 1, 1
defer func() {
suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess = priorDryRun, priorParallelTotal, priorParallelProcess
}()
reporter := reporters.NoopReporter{}
outputInterceptor = internal.NoopOutputInterceptor{}
client = nil
writer := GinkgoWriter.(*internal.Writer)

err := global.Suite.BuildTree()
err = global.Suite.BuildTree()
exitIfErr(err)
suitePath, err := os.Getwd()
exitIfErr(err)
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3191,7 +3191,7 @@ For a more complete preview you can run `ginkgo --dry-run -v`. This compiles th

If, you need finer-grained control over previews you can use `PreviewSpecs` in your suite in lieu of `RunSpecs`. `PreviewSpecs` behaves like `--dry-run` in that it will compile the suite, build the spec tree, and then walk the tree while honoring any filter and randomization flags. However `PreviewSpecs` generates and returns a full [`Report` object](#reporting-nodes---reportbeforesuite-and-reportaftersuite) that can be manipulated and inspected as needed. Specs that will be run will have `State = SpecStatePassed` and specs that will be skipped will have `SpecStateSkipped`.

Currently you must run in series to invoke `PreviewSpecs` and you cannot run both `PreviewSpecs` and `RunSpecs` in the same suite. If you are opting into `PreviewSpecs` in lieu of `--dry-run` one suggested pattern is to key off of the `--dry-run` configuration to run `PreviewSpecs` instead of `RunSpecs`:
If you are opting into `PreviewSpecs` in lieu of `--dry-run` one suggested pattern is to key off of the `--dry-run` configuration to run `PreviewSpecs` instead of `RunSpecs`:

```go
func TestMySuite(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import (

func TestPreviewFixture(t *testing.T) {
RegisterFailHandler(Fail)
if os.Getenv("RUN") == "true" {
RunSpecs(t, "PreviewFixture Suite", Label("suite-label"))
}
if os.Getenv("PREVIEW") == "true" {
report := PreviewSpecs("PreviewFixture Suite", Label("suite-label"))
for _, spec := range report.SpecReports {
fmt.Println(spec.State, spec.FullText())
}
}
if os.Getenv("RUN") == "true" {
RunSpecs(t, "PreviewFixture Suite", Label("suite-label"))
}
}

var _ = Describe("specs", func() {
Expand Down
24 changes: 16 additions & 8 deletions integration/preview_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package integration_test

import (
"fmt"
"os"

. "github.com/onsi/ginkgo/v2"
Expand All @@ -25,21 +26,28 @@ var _ = Describe("Preview", func() {
Ω(session).Should(gbytes.Say("skipped specs D"))
})

It("fails if running in parallel", func() {
It("succeeds if you attempt to both run and preview specs", func() {
os.Setenv("PREVIEW", "true")
DeferCleanup(os.Unsetenv, "PREVIEW")
session := startGinkgo(fm.PathTo("preview"), "--procs=2")
Eventually(session).Should(gexec.Exit(1))
Ω(session.Err).Should(gbytes.Say(`Ginkgo only supports PreviewSpecs\(\) in serial mode\.`))
os.Setenv("RUN", "true")
DeferCleanup(os.Unsetenv, "RUN")
session := startGinkgo(fm.PathTo("preview"))
Eventually(session).Should(gexec.Exit(0))
Ω(session).Should(gbytes.Say(`passed specs A`))
Ω(session).Should(gbytes.Say(`passed specs B`))
Ω(session).Should(gbytes.Say(`passed specs C`))
Ω(session).Should(gbytes.Say(`passed specs D`))
Ω(session).Should(gbytes.Say(`Ran 4 of 4 Specs`))
})

It("fails if you attempt to both run and preview specs", func() {
It("works if you run in parallel", func() {
os.Setenv("PREVIEW", "true")
DeferCleanup(os.Unsetenv, "PREVIEW")
os.Setenv("RUN", "true")
DeferCleanup(os.Unsetenv, "RUN")
session := startGinkgo(fm.PathTo("preview"))
Eventually(session).Should(gexec.Exit(1))
Ω(session).Should(gbytes.Say(`It looks like you are calling RunSpecs and PreviewSpecs in the same invocation`))
session := startGinkgo(fm.PathTo("preview"), "-p")
Eventually(session).Should(gexec.Exit(0))
fmt.Println(string(session.Out.Contents()))
Ω(session).Should(gbytes.Say(`Ran 4 of 4 Specs`))
})
})
11 changes: 11 additions & 0 deletions internal/global/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

var Suite *internal.Suite
var Failer *internal.Failer
var backupSuite *internal.Suite

func init() {
InitializeGlobals()
Expand All @@ -15,3 +16,13 @@ func InitializeGlobals() {
Failer = internal.NewFailer()
Suite = internal.NewSuite()
}

func PushClone() error {
var err error
backupSuite, err = Suite.Clone()
return err
}

func PopClone() {
Suite = backupSuite
}
6 changes: 6 additions & 0 deletions internal/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,12 @@ func (n Node) IsZero() bool {
/* Nodes */
type Nodes []Node

func (n Nodes) Clone() Nodes {
nodes := make(Nodes, len(n))
copy(nodes, n)
return nodes
}

func (n Nodes) CopyAppend(nodes ...Node) Nodes {
numN := len(n)
out := make(Nodes, numN+len(nodes))
Expand Down
16 changes: 16 additions & 0 deletions internal/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,22 @@ var _ = Describe("Node", func() {
})

var _ = Describe("Nodes", func() {
Describe("Clone", func() {
var n1, n2, n3, n4 Node

BeforeEach(func() {
n1, n2, n3, n4 = N(), N(), N(), N()
})

It("clones the slice", func() {
original := Nodes{n1, n2, n3}
clone := original.Clone()
Ω(original).Should(Equal(clone))
clone[2] = n4
Ω(original).Should(Equal(Nodes{n1, n2, n3}))
})
})

Describe("CopyAppend", func() {
var n1, n2, n3, n4 Node

Expand Down
14 changes: 14 additions & 0 deletions internal/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ func NewSuite() *Suite {
}
}

func (suite *Suite) Clone() (*Suite, error) {
if suite.phase != PhaseBuildTopLevel {
return nil, fmt.Errorf("cnanot clone suite after tree has been built")
}
return &Suite{
tree: &TreeNode{},
phase: PhaseBuildTopLevel,
ProgressReporterManager: NewProgressReporterManager(),
topLevelContainers: suite.topLevelContainers.Clone(),
suiteNodes: suite.suiteNodes.Clone(),
selectiveLock: &sync.Mutex{},
}, nil
}

func (suite *Suite) BuildTree() error {
// During PhaseBuildTopLevel, the top level containers are stored in suite.topLevelCotainers and entered
// We now enter PhaseBuildTree where these top level containers are entered and added to the spec tree
Expand Down
53 changes: 51 additions & 2 deletions internal/suite_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package internal_test

import (
"fmt"
"io"

. "github.com/onsi/ginkgo/v2"
Expand Down Expand Up @@ -57,7 +56,6 @@ var _ = Describe("Suite", func() {
})

It("only traverses top-level containers when told to BuildTree", func() {
fmt.Fprintln(GinkgoWriter, "HELLO!")
Ω(rt).Should(HaveTrackedNothing())
Ω(suite.BuildTree()).Should(Succeed())
Ω(rt).Should(HaveTracked("traversing outer", "traversing nested"))
Expand All @@ -72,6 +70,57 @@ var _ = Describe("Suite", func() {
})
})

Describe("cloning a suite", func() {
var err1, err2, err3 error
BeforeEach(func() {
suite.PushNode(N(types.NodeTypeBeforeSuite, "before suite", func() {
rt.Run("before-suite")
}))
err1 = suite.PushNode(N(ntCon, "a top-level container", func() {
rt.Run("traversing outer")
err2 = suite.PushNode(N(ntCon, "a nested container", func() {
rt.Run("traversing nested")
err3 = suite.PushNode(N(ntIt, "an it", rt.T("running it")))
}))
}))
})

It("fails if the tree has already been built", func() {
Ω(suite.BuildTree()).Should(Succeed())
_, err := suite.Clone()
Ω(err).Should(MatchError("cnanot clone suite after tree has been built"))
})

It("generates the same tree as the original", func() {
clone, err := suite.Clone()
Ω(err).ShouldNot(HaveOccurred())

Ω(suite.BuildTree()).Should(Succeed())
Ω(rt).Should(HaveTracked("traversing outer", "traversing nested"))
rt.Reset()
suite.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf)
Ω(rt).Should(HaveTracked("before-suite", "running it"))

Ω(err1).ShouldNot(HaveOccurred())
Ω(err2).ShouldNot(HaveOccurred())
Ω(err3).ShouldNot(HaveOccurred())

suite = clone // this is what the swapping of globals looks in reality

rt.Reset()
Ω(clone.BuildTree()).Should(Succeed())
Ω(rt).Should(HaveTracked("traversing outer", "traversing nested"))
rt.Reset()
clone.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf)
Ω(rt).Should(HaveTracked("before-suite", "running it"))

Ω(err1).ShouldNot(HaveOccurred())
Ω(err2).ShouldNot(HaveOccurred())
Ω(err3).ShouldNot(HaveOccurred())
})

})

Describe("InRunPhase", func() {
It("returns true when in the run phase and false when not in the run phase", func() {
falsey := true
Expand Down
15 changes: 0 additions & 15 deletions types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,6 @@ func (g ginkgoErrors) RerunningSuite() error {
}
}

func (g ginkgoErrors) RunAndPreviewSuite() error {
return GinkgoError{
Heading: "Running and Previewing Suite",
Message: formatter.F(`It looks like you are calling RunSpecs and PreviewSpecs in the same invocation of Ginkgo. Ginkgo does not currently support that. Please change your code to only call one or the other.`),
DocLink: "previewing-specs",
}
}

/* Tree construction errors */

func (g ginkgoErrors) PushingNodeInRunPhase(nodeType NodeType, cl CodeLocation) error {
Expand Down Expand Up @@ -586,13 +578,6 @@ func (g ginkgoErrors) DryRunInParallelConfiguration() error {
}
}

func (g ginkgoErrors) PreviewInParallelConfiguration() error {
return GinkgoError{
Heading: "Ginkgo only supports PreviewSpecs() in serial mode.",
Message: "Please try running ginkgo again, but without -p or -procs to ensure the suite is running in series.",
}
}

func (g ginkgoErrors) GracePeriodCannotBeZero() error {
return GinkgoError{
Heading: "Ginkgo requires a positive --grace-period.",
Expand Down

0 comments on commit 6c84b35

Please sign in to comment.