You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tea/tree.go

146 lines
3.3 KiB
Go

package tea
import (
"reflect"
"strings"
"testing"
)
// Run runs a tree of tests. Tests will be run recursively starting at the
// provided node and descending to all of its children. All of its parent nodes
// will also be run since they are prerequisites, but none of its sibling node
// will be executed.
func Run(t *testing.T, tree *Tree) {
t.Run(tree.name, func(t *testing.T) {
exec(t, tree)
after(t, tree)
if t.Failed() || t.Skipped() {
for _, child := range tree.children {
skip(t, child)
}
return
}
for _, child := range tree.children {
Run(t, child)
}
})
}
// exec runs the provided test and all of its ancestors in the provided testing
// context. exec returns the environment produced by running these tests.
func exec(t *testing.T, tree *Tree) *env {
if tree == nil {
return nil
}
if tree.parent == nil {
test := clone(tree.test)
test.Run(t)
return mkenv(test)
}
e := exec(t, tree.parent)
test := clone(tree.test)
e.load(test)
test.Run(t)
return e.save(test)
}
func after(t *testing.T, tree *Tree) {
if a, ok := tree.test.(After); ok {
a.After(t)
}
if tree.parent != nil {
after(t, tree.parent)
}
}
// skip skips the provided tree node as well as all of its children.
func skip(t *testing.T, tree *Tree) {
t.Run(tree.name, func(t *testing.T) {
for _, child := range tree.children {
skip(t, child)
}
t.Skip("tea skipped: dependency failed")
})
}
// New creates a new testing Tree starting with a root test. Given this root
// Tree node, consumers can add successive nodes to the tree as children of the
// root.
func New(test Test) *Tree {
return &Tree{
test: test,
name: parseName(test),
}
}
// Tree represents a node in a Tree of tests
type Tree struct {
test Test
name string
parent *Tree
children []*Tree
}
// Child creates a new Tree node as a child of the current tree node, returning
// the newly created child node.
func (t *Tree) Child(test Test) *Tree {
child := New(test)
child.parent = t
t.children = append(t.children, child)
return child
}
// clone clones a test value, yielding a new test value that can be executed
// and mutated such that the original is not mutated.
func clone(t Test) Test {
srcV := reflect.ValueOf(t).Elem()
destV := reflect.New(srcV.Type())
destV.Elem().Set(srcV)
return destV.Interface().(Test)
}
// isSaveField takes a struct field and checks its tags for a save tag,
// indicating that the field's value should persist between tests
func isSaveField(f reflect.StructField) bool {
// PkgPath is empty string when the identifier is unexported.
if f.PkgPath != "" {
return false
}
parts := strings.Split(f.Tag.Get("tea"), ",")
for _, part := range parts {
if part == "save" {
return true
}
}
return false
}
// isLoadField takes a struct field and checks its tags for a load tag,
// indicating that the field's value should be populated by a saved value from
// a prior test in the chain.
func isLoadField(f reflect.StructField) bool {
// PkgPath is empty string when the identifier is unexported.
if f.PkgPath != "" {
return false
}
parts := strings.Split(f.Tag.Get("tea"), ",")
for _, part := range parts {
if part == "load" {
return true
}
}
return false
}
// parseName parses the name for a given test
func parseName(test Test) string {
if s, ok := test.(interface{ String() string }); ok {
return s.String()
}
return "???"
}