re-implementing the env system
the env system code is unecessarily fiddly. The current env system is the second env system, so the layer system will be the third implementation of the env system. the environment in which we run our tests is a layered environment like the scope in a programming language, but unlike with nested closures, tea permits the user to supply match tags to skip what would be stack frames in a system based on nested closures. It's more easily understood as being a system of layers, and that we skip non-matching layers. the env system also had the problem that it was a tree that was dynamically constructed as a side-effect of the execution tree, but each chain only needs to read its own ancestry in the environment tree. Having a single tree poses challenges for parellelizing the execution of different test chains, so instead, I would rather keep a list of layers for each chain, instead of keeping a single environment tree that is shared across all chains in the run. Additionally, the env system was sparse; tests that save no data put no nodes in the env tree. I would like to be able to display to users the layers available to their test in certain error cases so that they can understand why a match may have failed. Right now, in practice, encountering a match failure in a test plan is confusing to understand.selection
parent
df36c8073c
commit
d5e30c26fe
@ -0,0 +1,7 @@
|
||||
package tea
|
||||
|
||||
// xchain is a chain of xnodes. an xchain is an execution plan for executing a
|
||||
// sequence of tests. somwhat ironically the nodes are actually in a slice.
|
||||
type xchain struct {
|
||||
xnodes []*xnode
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package tea
|
||||
|
||||
// generator functions for creating randomized test data.
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// alpha is an alphabet of letters to pick from when producing random strings.
|
||||
// the selected characters are human-readable without much visual ambiguity and
|
||||
// include some code points beyond the ascii range to make sure things don't
|
||||
// break on unicode input.
|
||||
var alpha = []rune("" +
|
||||
// lower-case ascii letters
|
||||
"abcdefghjkmnpqrstwxyz" +
|
||||
// upper-case ascii letters
|
||||
"ABCDEFGHIJKLMNPQRSTWXYZ" +
|
||||
// some digits
|
||||
"23456789" +
|
||||
// miscellaneous non-ascii characters
|
||||
"¢£¥ÐÑØæñþÆŁřƩλЖд")
|
||||
|
||||
func rstring(n int) string {
|
||||
r := make([]rune, n)
|
||||
for i, _ := range r {
|
||||
r[i] = alpha[rand.Intn(len(alpha))]
|
||||
}
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package tea
|
||||
|
||||
import "reflect"
|
||||
|
||||
type sval struct {
|
||||
name string
|
||||
val interface{}
|
||||
}
|
||||
|
||||
// layerData is an ordered list of key-value pairs. We preserve the ordering of
|
||||
// the fields from the structs that produced the layer so that viewing a list
|
||||
// of layers shows each layer from the sam struct in the same value-order,
|
||||
// which is the order those fields appear in their originating stuct (and not,
|
||||
// like, alphabetical order). Although a bit more tedious to work with
|
||||
// internally, this is intended to make it possible to write more descriptive,
|
||||
// easily understood PlanError messages.
|
||||
type layerData []sval
|
||||
|
||||
func (l layerData) get(key string) (val interface{}, present bool) {
|
||||
for _, pair := range l {
|
||||
if pair.name == key {
|
||||
return pair.val, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
type layer struct {
|
||||
origin *xnode
|
||||
saved layerData
|
||||
}
|
||||
|
||||
func makeLayerData(test Test) (layerData, error) {
|
||||
V := reflect.ValueOf(test)
|
||||
if V.Type().Kind() == reflect.Ptr {
|
||||
V = V.Elem()
|
||||
}
|
||||
T := V.Type()
|
||||
|
||||
if T.Kind() != reflect.Struct {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
fields, err := getSaveFields(T)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
// is this weird? maybe this is weird.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data := make(layerData, 0, len(fields))
|
||||
for _, f := range fields {
|
||||
fv := V.FieldByName(f.Name).Interface()
|
||||
data = append(data, sval{name: f.Name, val: fv})
|
||||
}
|
||||
return data, nil
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package tea
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLayerData(t *testing.T) {
|
||||
t.Run("non-struct tests produce empty layers", func(t *testing.T) {
|
||||
lr, err := makeLayerData(Pass)
|
||||
if len(lr) != 0 {
|
||||
t.Errorf("expected a nil layer but saw %v instead", lr)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from lay but saw %v instead", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("save tags on unexported fields are plan errors", func(t *testing.T) {
|
||||
type T struct {
|
||||
Passing
|
||||
count int `tea:"save"`
|
||||
}
|
||||
_, err := makeLayerData(T{})
|
||||
assertError(t, true, err, PlanError)
|
||||
})
|
||||
|
||||
t.Run("mixed exported/unexported fields still an error", func(t *testing.T) {
|
||||
type T struct {
|
||||
Passing
|
||||
count int `tea:"save"`
|
||||
Bar string `tea:"save"`
|
||||
}
|
||||
_, err := makeLayerData(T{})
|
||||
assertError(t, true, err, PlanError)
|
||||
})
|
||||
|
||||
t.Run("mixed exported/unexported fields still an error", func(t *testing.T) {
|
||||
type T struct {
|
||||
Passing
|
||||
count int `tea:"save"`
|
||||
bar string `tea:"save"`
|
||||
}
|
||||
_, err := makeLayerData(T{})
|
||||
assertError(t, true, err, PlanError)
|
||||
})
|
||||
|
||||
t.Run("save one int", func(t *testing.T) {
|
||||
type T struct {
|
||||
Passing
|
||||
Count int `tea:"save"`
|
||||
}
|
||||
test := T{Count: rand.Int()}
|
||||
data, err := makeLayerData(test)
|
||||
assertNoError(t, true, err)
|
||||
if len(data) == 0 {
|
||||
t.Fatalf("expected nonempty layer, saw empty layer instead")
|
||||
}
|
||||
if v, ok := data.get("Count"); !ok {
|
||||
t.Errorf("layer data is missing expected field Count")
|
||||
} else {
|
||||
if v != test.Count {
|
||||
t.Errorf("layer data expected Count value of %d but saw %d instead", test.Count, v)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("an int and a string", func(t *testing.T) {
|
||||
type T struct {
|
||||
Passing
|
||||
Count int `tea:"save"`
|
||||
Name string `tea:"save"`
|
||||
}
|
||||
test := T{Count: rand.Int(), Name: rstring(8)}
|
||||
data, err := makeLayerData(test)
|
||||
assertNoError(t, true, err)
|
||||
if len(data) == 0 {
|
||||
t.Fatalf("expected nonempty layer, saw empty layer instead")
|
||||
}
|
||||
if v, ok := data.get("Count"); !ok {
|
||||
t.Errorf("layer data is missing expected field Count")
|
||||
} else {
|
||||
if v != test.Count {
|
||||
t.Errorf("layer data expected Count value of %d but saw %d instead", test.Count, v)
|
||||
}
|
||||
}
|
||||
if v, ok := data.get("Name"); !ok {
|
||||
t.Errorf("layer data is missing expected field Count")
|
||||
} else {
|
||||
if v != test.Name {
|
||||
t.Errorf("layer data expected Name value of %s but saw %s instead", test.Name, v)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue