Compare commits
9 Commits
Author | SHA1 | Date |
---|---|---|
Jordan Orelli | d5e30c26fe | 4 years ago |
Jordan Orelli | df36c8073c | 4 years ago |
Jordan Orelli | 00ba88a520 | 4 years ago |
Jordan Orelli | 31fa2eb711 | 4 years ago |
Jordan Orelli | 500b02db34 | 4 years ago |
Jordan Orelli | 4d5a33dbdb | 4 years ago |
Jordan Orelli | 75849b2f36 | 4 years ago |
Jordan Orelli | 8659b230c8 | 4 years ago |
Jordan Orelli | acb92cc4d6 | 4 years ago |
@ -0,0 +1,74 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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,33 @@
|
||||
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")
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
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
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package tea
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestXLabels(t *testing.T) {
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
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
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
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