Path: blob/main/vendor/github.com/sclevine/spec/spec.go
2875 views
package spec12import (3"bytes"4"io"5"testing"6"time"7)89// G defines a group of specs.10// Unlike other testing libraries, it is re-evaluated for each subspec.11//12// Valid Options:13// Sequential, Random, Reverse, Parallel14// Local, Global, Flat, Nested15type G func(text string, f func(), opts ...Option)1617// Pend skips all specs in the provided group.18//19// All Options are ignored.20func (g G) Pend(text string, f func(), _ ...Option) {21g(text, f, func(c *config) { c.pend = true })22}2324// Focus focuses the provided group.25// This skips all specs in the suite except the group and other focused specs.26//27// Valid Options:28// Sequential, Random, Reverse, Parallel29// Local, Global, Flat, Nested30func (g G) Focus(text string, f func(), opts ...Option) {31g(text, f, append(opts, func(c *config) { c.focus = true })...)32}3334// S defines a spec.35//36// Valid Options: Parallel37type S func(text string, f func(), opts ...Option)3839// Before runs a function before each spec in the group.40func (s S) Before(f func()) {41s("", f, func(c *config) { c.before = true })42}4344// After runs a function after each spec in the group.45func (s S) After(f func()) {46s("", f, func(c *config) { c.after = true })47}4849// Pend skips the provided spec.50//51// All Options are ignored.52func (s S) Pend(text string, f func(), _ ...Option) {53s(text, f, func(c *config) { c.pend = true })54}5556// Focus focuses the provided spec.57// This skips all specs in the suite except the spec and other focused specs.58//59// Valid Options: Parallel60func (s S) Focus(text string, f func(), opts ...Option) {61s(text, f, append(opts, func(c *config) { c.focus = true })...)62}6364// Out provides an dedicated writer for the test to store output.65// Reporters usually display the contents on test failure.66//67// Valid context: inside S blocks only, nil elsewhere68func (s S) Out() io.Writer {69var out io.Writer70s("", nil, func(c *config) {71c.out = func(w io.Writer) {72out = w73}74})75return out76}7778// Suite defines a top-level group of specs within a suite.79// Suite behaves like a top-level version of G.80// Unlike other testing libraries, it is re-evaluated for each subspec.81//82// Valid Options:83// Sequential, Random, Reverse, Parallel84// Local, Global, Flat, Nested85type Suite func(text string, f func(*testing.T, G, S), opts ...Option) bool8687// Before runs a function before each spec in the suite.88func (s Suite) Before(f func(*testing.T)) bool {89return s("", func(t *testing.T, _ G, _ S) {90t.Helper()91f(t)92}, func(c *config) { c.before = true })93}9495// After runs a function after each spec in the suite.96func (s Suite) After(f func(*testing.T)) bool {97return s("", func(t *testing.T, _ G, _ S) {98t.Helper()99f(t)100}, func(c *config) { c.after = true })101}102103// Pend skips the provided top-level group of specs.104//105// All Options are ignored.106func (s Suite) Pend(text string, f func(*testing.T, G, S), _ ...Option) bool {107return s(text, f, func(c *config) { c.pend = true })108}109110// Focus focuses the provided top-level group.111// This skips all specs in the suite except the group and other focused specs.112//113// Valid Options:114// Sequential, Random, Reverse, Parallel115// Local, Global, Flat, Nested116func (s Suite) Focus(text string, f func(*testing.T, G, S), opts ...Option) bool {117return s(text, f, append(opts, func(c *config) { c.focus = true })...)118}119120// Run executes the specs defined in each top-level group of the suite.121func (s Suite) Run(t *testing.T) bool {122t.Helper()123return s("", nil, func(c *config) { c.t = t })124}125126// New creates an empty suite and returns an Suite function.127// The Suite function may be called to add top-level groups to the suite.128// The suite may be executed with Suite.Run.129//130// Valid Options:131// Sequential, Random, Reverse, Parallel132// Local, Global, Flat, Nested133// Seed, Report134func New(text string, opts ...Option) Suite {135var fs []func(*testing.T, G, S)136return func(newText string, f func(*testing.T, G, S), newOpts ...Option) bool {137cfg := options(newOpts).apply()138if cfg.t == nil {139fs = append(fs, func(t *testing.T, g G, s S) {140var do func(string, func(), ...Option) = g141if cfg.before || cfg.after {142do = s143}144do(newText, func() { f(t, g, s) }, newOpts...)145})146return true147}148cfg.t.Helper()149return Run(cfg.t, text, func(t *testing.T, g G, s S) {150for _, f := range fs {151f(t, g, s)152}153}, opts...)154}155}156157// Run immediately executes the provided specs as a suite.158// Unlike other testing libraries, it is re-evaluated for each spec.159//160// Valid Options:161// Sequential, Random, Reverse, Parallel162// Local, Global, Flat, Nested163// Seed, Report164func Run(t *testing.T, text string, f func(*testing.T, G, S), opts ...Option) bool {165t.Helper()166cfg := options(opts).apply()167n := &node{168text: []string{text},169seed: defaultZero64(cfg.seed, time.Now().Unix()),170order: cfg.order.or(orderSequential),171scope: cfg.scope.or(scopeLocal),172nest: cfg.nest.or(nestOff),173pend: cfg.pend,174focus: cfg.focus,175}176report := cfg.report177plan := n.parse(f)178179var specs chan Spec180if report != nil {181report.Start(t, plan)182specs = make(chan Spec, plan.Total)183done := make(chan struct{})184defer func() {185close(specs)186<-done187}()188go func() {189report.Specs(t, specs)190close(done)191}()192}193194return n.run(t, func(t *testing.T, n node) {195t.Helper()196buffer := &bytes.Buffer{}197defer func() {198if specs == nil {199return200}201specs <- Spec{202Text: n.text,203Failed: t.Failed(),204Skipped: t.Skipped(),205Focused: n.focus,206Parallel: n.order == orderParallel,207Out: buffer,208}209}()210switch {211case n.pend, plan.HasFocus && !n.focus:212t.SkipNow()213case n.order == orderParallel:214t.Parallel()215}216217var spec, group func()218hooks := newHooks()219group = func() {}220221f(t, func(_ string, f func(), _ ...Option) {222switch {223case len(n.loc) == 1, n.loc[0] > 0:224n.loc[0]--225case n.loc[0] == 0:226group = func() {227n.loc = n.loc[1:]228hooks.next()229group = func() {}230f()231group()232}233n.loc[0]--234}235}, func(_ string, f func(), opts ...Option) {236cfg := options(opts).apply()237switch {238case cfg.out != nil:239cfg.out(buffer)240case cfg.before:241hooks.before(f)242case cfg.after:243hooks.after(f)244case spec != nil:245case len(n.loc) > 1, n.loc[0] > 0:246n.loc[0]--247default:248spec = f249}250})251group()252253if spec == nil {254t.Fatal("Failed to locate spec.")255}256hooks.run(t, spec)257})258}259260type specHooks struct {261first, last *specHook262}263264type specHook struct {265before, after []func()266next *specHook267}268269func newHooks() specHooks {270h := &specHook{}271return specHooks{first: h, last: h}272}273274func (s specHooks) run(t *testing.T, spec func()) {275t.Helper()276for h := s.first; h != nil; h = h.next {277defer run(t, h.after...)278run(t, h.before...)279}280run(t, spec)281}282283func (s specHooks) before(f func()) {284s.last.before = append(s.last.before, f)285}286287func (s specHooks) after(f func()) {288s.last.after = append(s.last.after, f)289}290291func (s *specHooks) next() {292s.last.next = &specHook{}293s.last = s.last.next294}295296func run(t *testing.T, fs ...func()) {297t.Helper()298for _, f := range fs {299f()300}301}302303// Pend skips all specs in the top-level group.304//305// All Options are ignored.306func Pend(t *testing.T, text string, f func(*testing.T, G, S), _ ...Option) bool {307t.Helper()308return Run(t, text, f, func(c *config) { c.pend = true })309}310311// Focus focuses every spec in the provided suite.312// This is useful as a shortcut for unfocusing all focused specs.313//314// Valid Options:315// Sequential, Random, Reverse, Parallel316// Local, Global, Flat, Nested317// Seed, Report318func Focus(t *testing.T, text string, f func(*testing.T, G, S), opts ...Option) bool {319t.Helper()320return Run(t, text, f, append(opts, func(c *config) { c.focus = true })...)321}322323// A Plan provides a Reporter with information about a suite.324type Plan struct {325Text string326Total int327Pending int328Focused int329Seed int64330HasRandom bool331HasFocus bool332}333334// A Spec provides a Reporter with information about a spec immediately after335// the spec completes.336type Spec struct {337Text []string338Failed bool339Skipped bool340Focused bool341Parallel bool342Out io.Reader343}344345// A Reporter is provided with information about a suite as it runs.346type Reporter interface {347348// Start provides the Reporter with a Plan that describes the suite.349// No specs will run until the Start method call finishes.350Start(*testing.T, Plan)351352// Specs provides the Reporter with a channel of Specs.353// The specs will start running concurrently with the Specs method call.354// The Run method will not complete until the Specs method call completes.355Specs(*testing.T, <-chan Spec)356}357358359