Skip to content

Commit

Permalink
deprecate "AsSuite" flag and instead favour Spec#AsSuite
Browse files Browse the repository at this point in the history
  • Loading branch information
adamluzsi committed May 2, 2024
1 parent 4aebe35 commit 17b65b5
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 34 deletions.
34 changes: 27 additions & 7 deletions Spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -669,9 +669,13 @@ func escapeName(s string) string {
return s
}

const panicMessageSpecSpec = `The "testcase.Spec#Spec" method is designed to attach a "testcase.Spec" used as a suite to a subcontext of another "testcase.Spec".
To achieve this, the current "testcase.Spec" needs to be created as a suite by providing "nil" for the "testing.TB" argument in "testcase.NewSpec".
Once the "Spec" is converted into a suite, you can use "testcase.Spec#Spec" as the function block for another "testcase.Spec" "#Context" call.`

func (spec *Spec) Spec(oth *Spec) {
if !spec.isSuite {
panic("Spec method is only allowed when testcase.Spec made with AsSuite option")
panic(panicMessageSpecSpec)
}
oth.testingTB.Helper()
isSuite := oth.isSuite
Expand All @@ -689,6 +693,9 @@ func (spec *Spec) getIsSuite() bool {
if s.isSuite {
return true
}
if s.testingTB == nil {
return true
}
}
return false
}
Expand All @@ -707,16 +714,29 @@ func (spec *Spec) hasTestRan() bool {

func checkSuite(tb testing.TB, opts []SpecOption) (testing.TB, []SpecOption) {
if tb == nil {
return internal.SuiteNullTB{}, append(opts, AsSuite())
return internal.SuiteNullTB{}, append(opts, specOptionFunc(func(s *Spec) {
s.isSuite = true
}))
}
return tb, opts
}

func (spec *Spec) AsSuite() SpecSuite { return SpecSuite{S: spec} }
func (spec *Spec) AsSuite(name ...string) SpecSuite {
return SpecSuite{N: strings.Join(name, " "), S: spec}
}

type SpecSuite struct{ S *Spec }
type SpecSuite struct {
N string
S *Spec
}

func (suite SpecSuite) Name() string { return suite.S.suiteName }
func (suite SpecSuite) Test(t *testing.T) { suite.S.Spec(NewSpec(t)) }
func (suite SpecSuite) Benchmark(b *testing.B) { suite.S.Spec(NewSpec(b)) }
func (suite SpecSuite) Name() string { return suite.N }
func (suite SpecSuite) Test(t *testing.T) { suite.run(t) }
func (suite SpecSuite) Benchmark(b *testing.B) { suite.run(b) }
func (suite SpecSuite) Spec(s *Spec) { suite.S.Spec(s) }

func (suite SpecSuite) run(tb testing.TB) {
s := NewSpec(tb)
defer s.Finish()
s.Context(suite.N, suite.Spec, Group(suite.N))
}
52 changes: 40 additions & 12 deletions Spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1393,8 +1393,39 @@ func TestSpec_spike(t *testing.T) {
}

func TestSpec_Spec(t *testing.T) {
t.Run("Spec implements Suite.Spec idiom that enables it to be mounted to a top-level spec", func(t *testing.T) {
var states []string
suite := testcase.NewSpec(nil)
suite.Test("A", func(t *testcase.T) {
states = append(states, "A")
})
suite.Context("1", func(s *testcase.Spec) {
s.Test("B", func(t *testcase.T) {
states = append(states, "B")
})

s.Context("2", func(s *testcase.Spec) {
s.Test("C", func(t *testcase.T) {
states = append(states, "C")
})
})
})

// if a Spec is a Suite, then it is not executed by default
assert.Empty(t, states)

// when the spec suite is mounted into a top-level Spec
tb := &doubles.TB{}
topLevelSpec := testcase.NewSpec(tb)
topLevelSpec.Context("mount-point", suite.Spec)
topLevelSpec.Finish()
tb.Finish()

// then execution is expected
assert.ContainExactly(t, []string{"A", "B", "C"}, states)
})
t.Run("runs only when Spec method is called", func(t *testing.T) {
s := testcase.NewSpec(nil, testcase.AsSuite())
s := testcase.NewSpec(nil)
s.Sequential()

var states []string
Expand Down Expand Up @@ -1425,21 +1456,18 @@ func TestSpec_Spec(t *testing.T) {
assert.ContainExactly(t, []string{"A", "B", "C"}, states)
})
t.Run("the only the passed testcase.Spec's testing.TB will be used during failure", func(t *testing.T) {
ogTB := &doubles.TB{}
s := testcase.NewSpec(ogTB, testcase.AsSuite())
s := testcase.NewSpec(nil)
s.Test("A", func(t *testcase.T) {
t.Fail()
})

// when Spec is called, then it will execute
dtb := &doubles.TB{}
s.Spec(testcase.NewSpec(dtb))

assert.False(t, ogTB.Failed(), "it was not expected that the testing.TB failed")
assert.True(t, dtb.IsFailed)
})
t.Run("options passed down to the target spec", func(t *testing.T) {
s := testcase.NewSpec(nil, testcase.AsSuite(), testcase.Flaky(42))
s := testcase.NewSpec(nil, testcase.Flaky(42))

var once sync.Once
s.Test("", func(t *testcase.T) {
Expand All @@ -1453,10 +1481,10 @@ func TestSpec_Spec(t *testing.T) {
})
t.Run("mounting a Suite into another Suite should still not execute", func(t *testing.T) {
var ran bool
s1 := testcase.NewSpec(nil, testcase.AsSuite())
s1 := testcase.NewSpec(nil)
s1.Test("", func(t *testcase.T) { ran = true })

s2 := testcase.NewSpec(nil, testcase.AsSuite())
s2 := testcase.NewSpec(nil)
s1.Spec(s2) // s1 merge into s2

assert.False(t, ran)
Expand Down Expand Up @@ -1532,17 +1560,17 @@ func TestSpec_AsSuite(t *testing.T) {
func TestRunSuite_spectAsSuite(t *testing.T) {
var name1, name2 string

suite1 := testcase.NewSpec(nil, testcase.AsSuite("Suite-namE-1"))
suite1 := testcase.NewSpec(nil)
suite1.Test("tst1", func(t *testcase.T) { name1 = t.Name() })

suite2 := testcase.NewSpec(nil, testcase.AsSuite("Suite-namE-2"))
suite2 := testcase.NewSpec(nil)
suite2.Test("tst2", func(t *testcase.T) { name2 = t.Name() })

dtb := &doubles.TB{}
testcase.RunSuite(dtb, suite1, suite2.AsSuite())

assert.True(t, strings.HasSuffix(name1, "Suite-namE-1/tst1"))
assert.True(t, strings.HasSuffix(name2, "Suite-namE-2/tst2"))
assert.True(t, strings.HasSuffix(name1, "tst1"))
assert.True(t, strings.HasSuffix(name2, "tst2"))
}

func TestSpec_Benchmark(t *testing.T) {
Expand Down
12 changes: 0 additions & 12 deletions Suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,11 @@ package testcase

import (
"fmt"
"strings"
"testing"

"go.llib.dev/testcase/internal"
)

// AsSuite will flag the Spec as a Suite.
// Calling AsSuite will delay test until the Spec.Spec function is called
func AsSuite(name ...string) SpecOption {
return specOptionFunc(func(s *Spec) {
s.isSuite = true
if 0 < len(name) {
s.suiteName = strings.Join(name, " ")
}
})
}

// Suite meant to represent a testing suite.
// A test Suite is a collection of test cases.
// In a test suite, the test cases are organized in a logical order.
Expand Down
27 changes: 24 additions & 3 deletions examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1512,14 +1512,14 @@ func Example_global_Before() {
})
}

func ExampleAsSuite() {
func ExampleSpec_asSuite() {
var tb testing.TB
s := testcase.NewSpec(tb)
s.Context("my example testing suite", exampleSuite().Spec)
}

func exampleSuite() testcase.Suite {
s := testcase.NewSpec(nil, testcase.AsSuite())
s := testcase.NewSpec(nil) // nil as testing TB
s.Test("foo", func(t *testcase.T) {
// OK
})
Expand All @@ -1537,7 +1537,7 @@ func ExampleSpec_AsSuite() {
}

func exampleOpenSuite() testcase.OpenSuite {
s := testcase.NewSpec(nil, testcase.AsSuite())
s := testcase.NewSpec(nil)
s.Test("foo", func(t *testcase.T) {
// OK
})
Expand All @@ -1555,3 +1555,24 @@ func ExampleSpec_Benchmark() {
// OK
})
}

func ExampleSpec_Spec() {
sharedSuite := testcase.NewSpec(nil)
sharedSuite.Test("1", func(t *testcase.T) {})
sharedSuite.Test("2", func(t *testcase.T) {})
sharedSuite.Test("3", func(t *testcase.T) {})

{
var tb testing.TB // real one, not nil
s := testcase.NewSpec(tb)
s.Describe("something", sharedSuite.Spec)
}
{
var t *testing.T // func Test(t *testing.T)
sharedSuite.AsSuite("x").Test(t)
}
{
var b *testing.B // func Benchmark(b *testing.B)
sharedSuite.AsSuite("x").Benchmark(b)
}
}

0 comments on commit 17b65b5

Please sign in to comment.