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. // // Since Run will walk all of the descendents of the provided node, a typical // usage would be to write a top-level Go test which is a single tree of tea // Test values. You would then call Run just once, by supplying to Run the root // node of your tree. func Run(t *testing.T, tree *Tree) { t.Run(tree.name, func(t *testing.T) { history, _ := exec(t, tree) for _, test := range history { if a, ok := test.(After); ok { a.After(t) } } 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) ([]Test, *env) { if tree == nil { return nil, nil } if tree.parent == nil { test := clone(tree.test) test.Run(t) return []Test{test}, mkenv(test) } history, e := exec(t, tree.parent) test := clone(tree.test) if err := e.load(test); err != nil { t.Errorf("test plan failed: %s", err) } else { test.Run(t) } return append([]Test{test}, history...), e.save(test) } // 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. Callers create Tree elements in // one of two ways: by calling New to create a new Tree with the provided test // as its root, or by calling the Child method on an existing Tree to add a // child node to the tree. 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 } func isMatchField(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 == "match" { return true } } return false } func getMatchFields(t reflect.Type) []reflect.StructField { var fields []reflect.StructField for i := 0; i < t.NumField(); i++ { f := t.Field(i) if isMatchField(f) { fields = append(fields, f) } } return fields }