From 2c68d4dbd55cffbb809ea6412dc3b6838d311475 Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Sat, 25 Jul 2020 21:22:07 +0000 Subject: [PATCH] can copy field values between successive tests this isn't going to work permanently, if there's a save field in one test, then an intermediate test without that field, then a load field, it won't be visible at the end test. --- tea/tree.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++----- tea_test.go | 10 ++++----- 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/tea/tree.go b/tea/tree.go index 052e508..18708db 100644 --- a/tea/tree.go +++ b/tea/tree.go @@ -2,15 +2,15 @@ package tea import ( "reflect" + "strings" "testing" ) // Run runs a tree of tests, starting from its root. func Run(t *testing.T, tree *Tree) { t.Run(tree.name, func(t *testing.T) { - setup(t, tree) - - clone(tree.test).Run(t) + test := setup(t, tree) + test.Run(t) for _, child := range tree.children { if t.Failed() || t.Skipped() { @@ -22,11 +22,14 @@ func Run(t *testing.T, tree *Tree) { }) } -func setup(t *testing.T, tree *Tree) { +func setup(t *testing.T, tree *Tree) Test { + test := clone(tree.test) if tree.parent != nil { - setup(t, tree.parent) - clone(tree.parent.test).Run(t) + p := setup(t, tree.parent) + p.Run(t) + test = merge(test, p) } + return test } func skip(t *testing.T, tree *Tree) { @@ -59,6 +62,8 @@ func (t *Tree) Child(test Test) *Tree { 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()) @@ -66,6 +71,53 @@ func clone(t Test) Test { return destV.Interface().(Test) } +// merge merges into dest the fields on the src test that are marked as worth +// saving +func merge(dest Test, src Test) Test { + destV := reflect.ValueOf(dest).Elem() + srcV := reflect.ValueOf(src).Elem() + + for i := 0; i < srcV.NumField(); i++ { + sf := srcV.Type().Field(i) + if isSaveField(sf) { + df, ok := destV.Type().FieldByName(sf.Name) + if ok && isLoadField(df) { + if sf.Type == df.Type { + sfv := srcV.FieldByName(sf.Name) + dfv := destV.FieldByName(sf.Name) + dfv.Set(sfv) + } + } + } + } + return dest +} + +// 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 { + 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 { + parts := strings.Split(f.Tag.Get("tea"), ",") + for _, part := range parts { + if part == "load" { + return true + } + } + return false +} + func parseName(test Test) string { if s, ok := test.(interface{ String() string }); ok { return s.String() diff --git a/tea_test.go b/tea_test.go index 8882288..8b2457a 100644 --- a/tea_test.go +++ b/tea_test.go @@ -10,20 +10,21 @@ import ( ) type testThingSetup struct { - thing *Thing + Thing *Thing `tea:"save"` } func (test *testThingSetup) Run(t *testing.T) { t.Logf("[%s] running testThingSetup", t.Name()) - if test.thing != nil { + if test.Thing != nil { t.Fatal("should be nil") } - test.thing = new(Thing) + test.Thing = new(Thing) } func (test testThingSetup) String() string { return "thingSetup" } type setKey struct { + Thing *Thing `tea:"load"` key string value string bad bool @@ -35,9 +36,8 @@ func (test setKey) String() string { func (test *setKey) Run(t *testing.T) { t.Logf("[%s] running setKey key: %q value: %q", t.Name(), test.key, test.value) - thing := new(Thing) - err := thing.Set(test.key, test.value) + err := test.Thing.Set(test.key, test.value) if !test.bad && err != nil { t.Errorf("should be able to set %q=%q but saw error %v", test.key, test.value, err) }