Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Jordan Orelli | 7363c4b2c4 | 4 years ago |
@ -1,74 +0,0 @@
|
|||||||
package tea
|
|
||||||
|
|
||||||
// this is just a collection of ... reusable assertions for the unit tests for
|
|
||||||
// tea itself.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type labelChecker struct {
|
|
||||||
name string
|
|
||||||
wanted map[string]bool // all the strings we want
|
|
||||||
found map[string]bool // all the strings we've found
|
|
||||||
}
|
|
||||||
|
|
||||||
func wantStrings(name string, want ...string) labelChecker {
|
|
||||||
l := newLabelChecker(name)
|
|
||||||
l.want(want...)
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func newLabelChecker(name string) labelChecker {
|
|
||||||
return labelChecker{
|
|
||||||
name: name,
|
|
||||||
wanted: make(map[string]bool),
|
|
||||||
found: make(map[string]bool),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *labelChecker) want(names ...string) {
|
|
||||||
for _, name := range names {
|
|
||||||
l.wanted[name] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *labelChecker) add(name string) {
|
|
||||||
l.found[name] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *labelChecker) report(t *testing.T) {
|
|
||||||
for name, _ := range l.found {
|
|
||||||
if l.wanted[name] {
|
|
||||||
t.Logf("%s saw expected value %s", l.name, name)
|
|
||||||
} else {
|
|
||||||
t.Errorf("%s saw unexpected value %s", l.name, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for name, _ := range l.wanted {
|
|
||||||
if !l.found[name] {
|
|
||||||
t.Errorf("%s missing expected value %s", l.name, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertError(t *testing.T, fatal bool, err error, target error) {
|
|
||||||
if !errors.Is(err, target) {
|
|
||||||
if fatal {
|
|
||||||
t.Fatalf("expected error to be %s, instead found: %s", target, err)
|
|
||||||
} else {
|
|
||||||
t.Errorf("expected error to be %s, instead found: %s", target, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertNoError(t *testing.T, fatal bool, err error) {
|
|
||||||
if err != nil {
|
|
||||||
if fatal {
|
|
||||||
t.Fatalf("encountered unexpected error: %v", err)
|
|
||||||
} else {
|
|
||||||
t.Fatalf("encountered unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package tea
|
|
||||||
|
|
||||||
// these constant tests are useful for testing things that manipulate the
|
|
||||||
// graph.
|
|
||||||
|
|
||||||
const (
|
|
||||||
A = Passing("A")
|
|
||||||
B = Passing("B")
|
|
||||||
C = Passing("C")
|
|
||||||
D = Passing("D")
|
|
||||||
E = Passing("E")
|
|
||||||
F = Passing("F")
|
|
||||||
G = Passing("G")
|
|
||||||
H = Passing("H")
|
|
||||||
I = Passing("I")
|
|
||||||
J = Passing("J")
|
|
||||||
K = Passing("K")
|
|
||||||
L = Passing("L")
|
|
||||||
M = Passing("M")
|
|
||||||
N = Passing("N")
|
|
||||||
O = Passing("O")
|
|
||||||
P = Passing("P")
|
|
||||||
Q = Passing("Q")
|
|
||||||
R = Passing("R")
|
|
||||||
S = Passing("S")
|
|
||||||
T = Passing("T")
|
|
||||||
U = Passing("U")
|
|
||||||
V = Passing("V")
|
|
||||||
W = Passing("W")
|
|
||||||
X = Passing("X")
|
|
||||||
Y = Passing("Y")
|
|
||||||
Z = Passing("Z")
|
|
||||||
)
|
|
@ -1,34 +0,0 @@
|
|||||||
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())
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,158 +0,0 @@
|
|||||||
package tea
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
var lastID int
|
|
||||||
|
|
||||||
func nextNodeID() int {
|
|
||||||
lastID++
|
|
||||||
return lastID
|
|
||||||
}
|
|
||||||
|
|
||||||
// lnode is a node in the logical graph. Developers create a logical graph in
|
|
||||||
// which nodes may have more than one parent. Each test value written by a
|
|
||||||
// developer appears as one logical node in the logical graph. The public
|
|
||||||
// documentation refers to the logical graph as simply the test graph.
|
|
||||||
type lnode struct {
|
|
||||||
id int
|
|
||||||
name string
|
|
||||||
xnodes []xnode
|
|
||||||
test Test
|
|
||||||
parents []*lnode
|
|
||||||
children []*lnode
|
|
||||||
}
|
|
||||||
|
|
||||||
// newLNode greates a new lnode with the provided test and the list of parents.
|
|
||||||
// If there are no parents, the lnode is a root node, and the provided test is
|
|
||||||
// run once. Otherwise, the provided test is used to create xnodes that are
|
|
||||||
// children of all of the provided parent nodes' xnodes.
|
|
||||||
func newLNode(test Test, sel Selection) *lnode {
|
|
||||||
if len(sel.nodes) == 0 {
|
|
||||||
return rootLNode(test)
|
|
||||||
}
|
|
||||||
|
|
||||||
node := lnode{
|
|
||||||
id: nextNodeID(),
|
|
||||||
name: parseName(test),
|
|
||||||
test: test,
|
|
||||||
parents: make([]*lnode, len(sel.nodes)),
|
|
||||||
}
|
|
||||||
// not sure if this copy is necessary
|
|
||||||
copy(node.parents, sel.nodes)
|
|
||||||
|
|
||||||
xID := 0
|
|
||||||
for _, parent := range node.parents {
|
|
||||||
parent.children = append(parent.children, &node)
|
|
||||||
for i, _ := range parent.xnodes {
|
|
||||||
x := xnode{
|
|
||||||
id: xID,
|
|
||||||
lnode: &node,
|
|
||||||
parent: &parent.xnodes[i],
|
|
||||||
}
|
|
||||||
node.xnodes = append(node.xnodes, x)
|
|
||||||
xID++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, x := range node.xnodes {
|
|
||||||
x.parent.children = append(x.parent.children, &node.xnodes[i])
|
|
||||||
}
|
|
||||||
return &node
|
|
||||||
}
|
|
||||||
|
|
||||||
// rootLNode creates a root lnode. This case is a lot simpler so I split it out
|
|
||||||
// to keep newLNode a little more readable.
|
|
||||||
func rootLNode(test Test) *lnode {
|
|
||||||
id := nextNodeID()
|
|
||||||
node := lnode{
|
|
||||||
id: id,
|
|
||||||
name: parseName(test),
|
|
||||||
test: test,
|
|
||||||
}
|
|
||||||
node.xnodes = []xnode{{id: 0, lnode: &node}}
|
|
||||||
return &node
|
|
||||||
}
|
|
||||||
|
|
||||||
// xnode is a node in the execution graph, representing one instance of a test
|
|
||||||
// to be executed. xnode is the unit test in tea. every xnode is either
|
|
||||||
// unparented or has one parent.
|
|
||||||
type xnode struct {
|
|
||||||
id int // id within the parent lnode
|
|
||||||
lnode *lnode // corresponding node in the logical test graph
|
|
||||||
parent *xnode
|
|
||||||
children []*xnode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *xnode) isOnlyTestInLNode() bool {
|
|
||||||
return len(x.lnode.xnodes) == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// label must be unique or some other shit will break, I'm using this as a way
|
|
||||||
// to globally identify xnodes, which may be very flawed and maybe I should
|
|
||||||
// have an actual global ID system.
|
|
||||||
func (x *xnode) label() string {
|
|
||||||
if x.parent == nil {
|
|
||||||
switch {
|
|
||||||
case len(x.lnode.children) < 10:
|
|
||||||
return fmt.Sprintf("%s.%d", x.lnode.name, x.id)
|
|
||||||
case len(x.lnode.children) < 100:
|
|
||||||
return fmt.Sprintf("%s.%02d", x.lnode.name, x.id)
|
|
||||||
case len(x.lnode.children) < 1000:
|
|
||||||
return fmt.Sprintf("%s.%03d", x.lnode.name, x.id)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%s.%04d", x.lnode.name, x.id)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch {
|
|
||||||
case len(x.lnode.children) < 10:
|
|
||||||
return fmt.Sprintf("%s.%d.%s", x.lnode.name, x.id, x.parent.lnode.name)
|
|
||||||
case len(x.lnode.children) < 100:
|
|
||||||
return fmt.Sprintf("%s.%02d.%s", x.lnode.name, x.id, x.parent.lnode.name)
|
|
||||||
case len(x.lnode.children) < 1000:
|
|
||||||
return fmt.Sprintf("%s.%03d.%s", x.lnode.name, x.id, x.parent.lnode.name)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%s.%04d.%s", x.lnode.name, x.id, x.parent.lnode.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ancestry gives a slice of xnodes beginning at the root of the x graph and
|
|
||||||
// terminating at the receiver xnode. The ancestry list of a leaf node in the x
|
|
||||||
// graph is a single chain of tests.
|
|
||||||
func (x *xnode) ancestry() []*xnode {
|
|
||||||
if x.parent == nil {
|
|
||||||
return []*xnode{x}
|
|
||||||
}
|
|
||||||
return append(x.parent.ancestry(), x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// descendents gives a slice of all xnodes whose ancestry includes the receiver
|
|
||||||
// xnode, in depth-first order.
|
|
||||||
func (x *xnode) descendents() []*xnode {
|
|
||||||
if len(x.children) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
descendents := make([]*xnode, 0, len(x.children))
|
|
||||||
for _, c := range x.children {
|
|
||||||
descendents = append(descendents, c)
|
|
||||||
descendents = append(descendents, c.descendents()...)
|
|
||||||
}
|
|
||||||
return descendents
|
|
||||||
}
|
|
||||||
|
|
||||||
// leaves descends the x graph from the receiver xnode, returning a slice
|
|
||||||
// containing all of the leaves of the x graph having the receiver x as an
|
|
||||||
// ancestor.
|
|
||||||
func (x *xnode) leaves() []*xnode {
|
|
||||||
if len(x.children) == 0 {
|
|
||||||
return []*xnode{x}
|
|
||||||
}
|
|
||||||
|
|
||||||
var leaves []*xnode
|
|
||||||
for _, child := range x.children {
|
|
||||||
leaves = append(leaves, child.leaves()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return leaves
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package tea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestXLabels(t *testing.T) {
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
package tea
|
|
||||||
|
|
||||||
func NewSelection(test Test) Selection {
|
|
||||||
node := newLNode(test, Selection{})
|
|
||||||
return Selection{nodes: []*lnode{node}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Selection represents a set of nodes in our graph.
|
|
||||||
type Selection struct {
|
|
||||||
nodes []*lnode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Selection) Child(test Test) Selection {
|
|
||||||
node := newLNode(test, s)
|
|
||||||
return Selection{nodes: []*lnode{node}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Selection) And(other Selection) Selection {
|
|
||||||
included := make(map[int]bool)
|
|
||||||
|
|
||||||
out := make([]*lnode, 0, len(s.nodes)+len(other.nodes))
|
|
||||||
for _, n := range append(s.nodes, other.nodes...) {
|
|
||||||
if !included[n.id] {
|
|
||||||
out = append(out, n)
|
|
||||||
included[n.id] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Selection{nodes: out}
|
|
||||||
}
|
|
||||||
|
|
||||||
// xnodes represents all xnodes in the selected lnodes
|
|
||||||
func (s Selection) xnodes() []*xnode {
|
|
||||||
xnodes := make([]*xnode, 0, s.countXNodes())
|
|
||||||
for _, L := range s.nodes {
|
|
||||||
for i, _ := range L.xnodes {
|
|
||||||
xnodes = append(xnodes, &L.xnodes[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return xnodes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Selection) countXNodes() int {
|
|
||||||
total := 0
|
|
||||||
for _, child := range s.nodes {
|
|
||||||
total += len(child.xnodes)
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
// xleaves looks at all of the selected xnodes, and for every selected xnode,
|
|
||||||
// traverses the x graph until we arrive at the set of all leaf nodes that have
|
|
||||||
// a selected ancestor. If the selection consists of the root node, the xleaves
|
|
||||||
// are all of the leaves of the x graph.
|
|
||||||
func (s *Selection) xleaves() []*xnode {
|
|
||||||
// honestly think that by definition every xnode in the selection has a
|
|
||||||
// non-overlapping set of leaves but thinking about this shit is extremely
|
|
||||||
// starting to hurt my brain so I'm going to write this in a way that's
|
|
||||||
// maybe very redundant.
|
|
||||||
|
|
||||||
seen := make(map[string]bool)
|
|
||||||
var leaves []*xnode
|
|
||||||
for _, x := range s.xnodes() {
|
|
||||||
for _, leaf := range x.leaves() {
|
|
||||||
if seen[leaf.label()] {
|
|
||||||
panic("double-counting leaves somehow")
|
|
||||||
}
|
|
||||||
seen[leaf.label()] = true
|
|
||||||
leaves = append(leaves, leaf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return leaves
|
|
||||||
}
|
|
@ -1,219 +0,0 @@
|
|||||||
package tea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type selectionTest struct {
|
|
||||||
label string
|
|
||||||
selection Selection
|
|
||||||
lnodes []string
|
|
||||||
xnodes []string
|
|
||||||
xleaves []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (test *selectionTest) Run(t *testing.T) {
|
|
||||||
LWanted := wantStrings("selected lnode names", test.lnodes...)
|
|
||||||
for _, L := range test.selection.nodes {
|
|
||||||
LWanted.add(L.name)
|
|
||||||
}
|
|
||||||
LWanted.report(t)
|
|
||||||
|
|
||||||
XWanted := wantStrings("selected xnode labels", test.xnodes...)
|
|
||||||
for _, X := range test.selection.xnodes() {
|
|
||||||
XWanted.add(X.label())
|
|
||||||
}
|
|
||||||
XWanted.report(t)
|
|
||||||
|
|
||||||
XLeavesWanted := wantStrings("leaf xnode labels", test.xleaves...)
|
|
||||||
for _, X := range test.selection.xnodes() {
|
|
||||||
for _, leaf := range X.leaves() {
|
|
||||||
XLeavesWanted.add(leaf.label())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
XLeavesWanted.report(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSelections(t *testing.T) {
|
|
||||||
tests := []selectionTest{
|
|
||||||
{
|
|
||||||
label: "new selection",
|
|
||||||
selection: NewSelection(A),
|
|
||||||
lnodes: []string{"A"},
|
|
||||||
xnodes: []string{"A.0"},
|
|
||||||
xleaves: []string{"A.0"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "root with one child",
|
|
||||||
selection: NewSelection(A).Child(B),
|
|
||||||
lnodes: []string{"B"},
|
|
||||||
xnodes: []string{"B.0.A"},
|
|
||||||
xleaves: []string{"B.0.A"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "two selected roots",
|
|
||||||
selection: NewSelection(A).And(NewSelection(B)),
|
|
||||||
lnodes: []string{"A", "B"},
|
|
||||||
xnodes: []string{"A.0", "B.0"},
|
|
||||||
xleaves: []string{"A.0", "B.0"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
add := func(fn func() selectionTest) { tests = append(tests, fn()) }
|
|
||||||
|
|
||||||
add(func() selectionTest {
|
|
||||||
root := NewSelection(A)
|
|
||||||
b := root.Child(B)
|
|
||||||
return selectionTest{
|
|
||||||
label: "root and child selected",
|
|
||||||
selection: root.And(b),
|
|
||||||
lnodes: []string{"A", "B"},
|
|
||||||
xnodes: []string{"A.0", "B.0.A"},
|
|
||||||
xleaves: []string{"B.0.A"},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
add(func() selectionTest {
|
|
||||||
root := NewSelection(A)
|
|
||||||
b := root.Child(B)
|
|
||||||
return selectionTest{
|
|
||||||
label: "an optional test",
|
|
||||||
selection: root.And(b).Child(C),
|
|
||||||
lnodes: []string{"C"},
|
|
||||||
xnodes: []string{"C.0.A", "C.1.B"},
|
|
||||||
xleaves: []string{"C.0.A", "C.1.B"},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
add(func() selectionTest {
|
|
||||||
root := NewSelection(A)
|
|
||||||
b := root.Child(B)
|
|
||||||
c := root.Child(C)
|
|
||||||
|
|
||||||
return selectionTest{
|
|
||||||
label: "two children selected",
|
|
||||||
selection: b.And(c),
|
|
||||||
lnodes: []string{"B", "C"},
|
|
||||||
xnodes: []string{"B.0.A", "C.0.A"},
|
|
||||||
xleaves: []string{"B.0.A", "C.0.A"},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
add(func() selectionTest {
|
|
||||||
root := NewSelection(A)
|
|
||||||
b := root.Child(B)
|
|
||||||
c := root.Child(C)
|
|
||||||
return selectionTest{
|
|
||||||
label: "a diamond test",
|
|
||||||
selection: b.And(c).Child(D),
|
|
||||||
lnodes: []string{"D"},
|
|
||||||
xnodes: []string{"D.0.B", "D.1.C"},
|
|
||||||
xleaves: []string{"D.0.B", "D.1.C"},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
add(func() selectionTest {
|
|
||||||
root := NewSelection(A)
|
|
||||||
b := root.Child(B)
|
|
||||||
c := root.Child(C)
|
|
||||||
d := b.And(c).Child(D)
|
|
||||||
return selectionTest{
|
|
||||||
label: "child of a node having multiple parents",
|
|
||||||
selection: d.Child(E),
|
|
||||||
lnodes: []string{"E"},
|
|
||||||
xnodes: []string{"E.0.D", "E.1.D"},
|
|
||||||
xleaves: []string{"E.0.D", "E.1.D"},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
add(func() selectionTest {
|
|
||||||
root := NewSelection(A)
|
|
||||||
b := root.Child(B)
|
|
||||||
c := root.Child(C)
|
|
||||||
d := b.And(c).Child(D)
|
|
||||||
d.Child(E)
|
|
||||||
return selectionTest{
|
|
||||||
label: "the root of a complex graph",
|
|
||||||
selection: root,
|
|
||||||
lnodes: []string{"A"},
|
|
||||||
xnodes: []string{"A.0"},
|
|
||||||
xleaves: []string{"E.0.D", "E.1.D"},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// A
|
|
||||||
// / \
|
|
||||||
// / \
|
|
||||||
// B C
|
|
||||||
// / \ / \
|
|
||||||
// / \ / \
|
|
||||||
// D E F
|
|
||||||
// / \ / \
|
|
||||||
// / \ / \
|
|
||||||
// G H I
|
|
||||||
// | | |
|
|
||||||
// | | |
|
|
||||||
// J K L
|
|
||||||
// | \ /
|
|
||||||
// | \ /
|
|
||||||
// M N
|
|
||||||
//
|
|
||||||
add(func() selectionTest {
|
|
||||||
root := NewSelection(A)
|
|
||||||
b := root.Child(B)
|
|
||||||
c := root.Child(C)
|
|
||||||
b.Child(D)
|
|
||||||
e := b.And(c).Child(E)
|
|
||||||
f := c.Child(F)
|
|
||||||
e.Child(G).Child(J).Child(M)
|
|
||||||
h := e.And(f).Child(H)
|
|
||||||
l := f.Child(I).Child(L)
|
|
||||||
k := h.Child(K)
|
|
||||||
k.And(l).Child(N)
|
|
||||||
return selectionTest{
|
|
||||||
label: "criss-crossing",
|
|
||||||
selection: root,
|
|
||||||
lnodes: []string{"A"},
|
|
||||||
xnodes: []string{"A.0"},
|
|
||||||
xleaves: []string{
|
|
||||||
"D.0.B", // A B D
|
|
||||||
"M.0.J", // A B E G J M
|
|
||||||
"M.1.J", // A C E G J M
|
|
||||||
"N.0.K", // A B E H K N
|
|
||||||
"N.1.K", // A C E H K N
|
|
||||||
"N.2.K", // A C F H K N
|
|
||||||
"N.3.L", // A C F I L N
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
add(func() selectionTest {
|
|
||||||
root := NewSelection(A)
|
|
||||||
b := root.Child(B)
|
|
||||||
c := root.Child(C)
|
|
||||||
b.Child(D)
|
|
||||||
e := b.And(c).Child(E)
|
|
||||||
f := c.Child(F)
|
|
||||||
e.Child(G).Child(J).Child(M)
|
|
||||||
h := e.And(f).Child(H)
|
|
||||||
l := f.Child(I).Child(L)
|
|
||||||
k := h.Child(K)
|
|
||||||
k.And(l).Child(N)
|
|
||||||
return selectionTest{
|
|
||||||
label: "criss-crossing-partial",
|
|
||||||
selection: e,
|
|
||||||
lnodes: []string{"E"},
|
|
||||||
xnodes: []string{"E.0.B", "E.1.C"},
|
|
||||||
xleaves: []string{
|
|
||||||
"M.0.J", // A B E G J M
|
|
||||||
"M.1.J", // A C E G J M
|
|
||||||
"N.0.K", // A B E H K N
|
|
||||||
"N.1.K", // A C E H K N
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.label, test.Run)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue