tools/src/cts/query: Add Tree
A tree of query to data. Has utilities for reducing the tree based on a custom merger function. Bug: dawn:1342 Change-Id: If1c0503be05ee04bcf55dd5bdc9aa3caf6fb56ee Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/87222 Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
parent
4c9b72b4fa
commit
1a10b73552
|
@ -14,7 +14,10 @@
|
|||
|
||||
package container
|
||||
|
||||
import "sort"
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Set is a generic unordered set, which wrap's go's builtin 'map'.
|
||||
// T is the set key, which must match the 'key' constraint.
|
||||
|
@ -98,3 +101,24 @@ func (s Set[T]) List() []T {
|
|||
sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
|
||||
return out
|
||||
}
|
||||
|
||||
// One returns a random item from the set, or an empty item if the set is empty.
|
||||
func (s Set[T]) One() T {
|
||||
for item := range s {
|
||||
return item
|
||||
}
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
|
||||
// Format writes the Target to the fmt.State
|
||||
func (s Set[T]) Format(f fmt.State, verb rune) {
|
||||
fmt.Fprint(f, "[")
|
||||
for i, item := range s.List() {
|
||||
if i > 0 {
|
||||
fmt.Fprint(f, ", ")
|
||||
}
|
||||
fmt.Fprint(f, item)
|
||||
}
|
||||
fmt.Fprint(f, "]")
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
package container_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"dawn.googlesource.com/dawn/tools/src/container"
|
||||
|
@ -143,3 +144,19 @@ func TestSetRemoveAll(t *testing.T) {
|
|||
expectEq(t, "len(s)", len(s), 1)
|
||||
expectEq(t, "s.List()", s.List(), []string{"b"})
|
||||
}
|
||||
|
||||
func TestSetOne(t *testing.T) {
|
||||
expectEq(t, "NewSet[string]().One()", container.NewSet[string]().One(), "")
|
||||
expectEq(t, `NewSet("x").One()`, container.NewSet("x").One(), "x")
|
||||
if got := container.NewSet("x", "y").One(); got != "x" && got != "y" {
|
||||
t.Errorf(`NewSet("x", "y").One() returned "%v"`, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat(t *testing.T) {
|
||||
expectEq(t, "NewSet[string]()", fmt.Sprint(container.NewSet[string]()), "[]")
|
||||
expectEq(t, `NewSet("x")`, fmt.Sprint(container.NewSet("x")), `[x]`)
|
||||
expectEq(t, `NewSet(1)`, fmt.Sprint(container.NewSet(1)), `[1]`)
|
||||
expectEq(t, `NewSet("y", "x")`, fmt.Sprint(container.NewSet("y", "x")), `[x, y]`)
|
||||
expectEq(t, `NewSet(3, 1, 2)`, fmt.Sprint(container.NewSet(3, 1, 2)), `[1, 2, 3]`)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2022 The Dawn Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package query
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ErrNoDataForQuery struct {
|
||||
Query Query
|
||||
}
|
||||
|
||||
func (e ErrNoDataForQuery) Error() string {
|
||||
return fmt.Sprintf("no data for query '%v'", e.Query)
|
||||
}
|
||||
|
||||
type ErrDuplicateData struct {
|
||||
Query Query
|
||||
}
|
||||
|
||||
func (e ErrDuplicateData) Error() string {
|
||||
return fmt.Sprintf("duplicate data '%v'", e.Query)
|
||||
}
|
|
@ -198,6 +198,18 @@ func (q Query) CaseParameters() CaseParameters {
|
|||
// Append returns the query with the additional strings appended to the target
|
||||
func (q Query) Append(t Target, n ...string) Query {
|
||||
switch t {
|
||||
case Suite:
|
||||
switch len(n) {
|
||||
case 0:
|
||||
return q
|
||||
case 1:
|
||||
if q.Suite != "" {
|
||||
panic("cannot append suite when query already contains suite")
|
||||
}
|
||||
return Query{Suite: n[0]}
|
||||
default:
|
||||
panic("cannot append more than one suite")
|
||||
}
|
||||
case Files:
|
||||
return q.AppendFiles(n...)
|
||||
case Tests:
|
||||
|
|
|
@ -0,0 +1,404 @@
|
|||
// Copyright 2022 The Dawn Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Tree holds a tree structure of Query to generic Data type.
|
||||
// Each separate suite, file, test of the query produces a separate tree node.
|
||||
// All cases of the query produce a single leaf tree node.
|
||||
type Tree[Data any] struct {
|
||||
TreeNode[Data]
|
||||
}
|
||||
|
||||
// TreeNode is a single node in the Tree
|
||||
type TreeNode[Data any] struct {
|
||||
// The full query of the node
|
||||
Query Query
|
||||
// The data associated with this node. nil is used to represent no-data.
|
||||
Data *Data
|
||||
// Children of the node. Keyed by query.Target and name.
|
||||
Children TreeNodeChildren[Data]
|
||||
}
|
||||
|
||||
// TreeNodeChildKey is the key used by TreeNode for the Children map
|
||||
type TreeNodeChildKey struct {
|
||||
// The child name. This is the string between `:` and `,` delimiters.
|
||||
// Note: that all test cases are held by a single TreeNode.
|
||||
Name string
|
||||
// The target type of the child. Examples:
|
||||
// Query | Target of 'child'
|
||||
// -----------------+--------------------
|
||||
// parent:child | Files
|
||||
// parent:x,child | Files
|
||||
// parent:x:child | Test
|
||||
// parent:x:y,child | Test
|
||||
// parent:x:y:child | Cases
|
||||
//
|
||||
// It's possible to have a directory and '.spec.ts' share the same name,
|
||||
// hence why we include the Target as part of the child key.
|
||||
Target Target
|
||||
}
|
||||
|
||||
// TreeNodeChildren is a map of TreeNodeChildKey to TreeNode pointer.
|
||||
// Data is the data type held by a TreeNode.
|
||||
type TreeNodeChildren[Data any] map[TreeNodeChildKey]*TreeNode[Data]
|
||||
|
||||
// sortedChildKeys returns all the sorted children keys.
|
||||
func (n *TreeNode[Data]) sortedChildKeys() []TreeNodeChildKey {
|
||||
keys := make([]TreeNodeChildKey, 0, len(n.Children))
|
||||
for key := range n.Children {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
a, b := keys[i], keys[j]
|
||||
switch {
|
||||
case a.Name < b.Name:
|
||||
return true
|
||||
case a.Name > b.Name:
|
||||
return false
|
||||
case a.Target < b.Target:
|
||||
return true
|
||||
case a.Target > b.Target:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
})
|
||||
return keys
|
||||
}
|
||||
|
||||
// traverse performs a depth-first-search of the tree calling f for each visited
|
||||
// node, starting with n, then visiting each of children in sorted order
|
||||
// (pre-order traversal).
|
||||
func (n *TreeNode[Data]) traverse(f func(n *TreeNode[Data]) error) error {
|
||||
if err := f(n); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, key := range n.sortedChildKeys() {
|
||||
if err := n.Children[key].traverse(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merger is a function used to merge the children nodes of a tree.
|
||||
// Merger is called with the Data of each child node. If the function returns a
|
||||
// non-nil Data pointer, then this is used as the merged result. If the function
|
||||
// returns nil, then the node will not be merged.
|
||||
type Merger[Data any] func([]Data) *Data
|
||||
|
||||
// merge collapses tree nodes based on child node data, using the function f.
|
||||
// merge operates on the leaf nodes first, working its way towards the root of
|
||||
// the tree.
|
||||
// Returns the merged target data for this node, or nil if the node is not a
|
||||
// leaf and its children has non-uniform data.
|
||||
func (n *TreeNode[Data]) merge(f Merger[Data]) *Data {
|
||||
// If the node is a leaf, then simply return the node's data.
|
||||
if len(n.Children) == 0 {
|
||||
return n.Data
|
||||
}
|
||||
|
||||
// Build a map of child target to merged child data.
|
||||
// A nil for the value indicates that one or more children could not merge.
|
||||
mergedChildren := map[Target][]Data{}
|
||||
for key, child := range n.Children {
|
||||
// Call merge() on the child. Even if we cannot merge this node, we want
|
||||
// to do this for all children so they can merge their sub-graphs.
|
||||
childData := child.merge(f)
|
||||
|
||||
if childData == nil {
|
||||
// If merge() returned nil, then the data could not be merged.
|
||||
// Mark the entire target as unmergeable.
|
||||
mergedChildren[key.Target] = nil
|
||||
continue
|
||||
}
|
||||
|
||||
// Fetch the merge list for this child's target.
|
||||
list, found := mergedChildren[key.Target]
|
||||
if !found {
|
||||
// First child with the given target?
|
||||
mergedChildren[key.Target] = []Data{*childData}
|
||||
continue
|
||||
}
|
||||
if list != nil {
|
||||
mergedChildren[key.Target] = append(list, *childData)
|
||||
}
|
||||
}
|
||||
|
||||
merge := func(in []Data) *Data {
|
||||
switch len(in) {
|
||||
case 0:
|
||||
return nil // nothing to merge.
|
||||
case 1:
|
||||
return &in[0] // merge of a single item results in that item
|
||||
default:
|
||||
return f(in)
|
||||
}
|
||||
}
|
||||
|
||||
// Might it possible to merge this node?
|
||||
maybeMergeable := true
|
||||
|
||||
// The merged data, per target
|
||||
mergedTargets := map[Target]Data{}
|
||||
|
||||
// Attempt to merge each of the target's data
|
||||
for target, list := range mergedChildren {
|
||||
if list != nil { // nil == unmergeable target
|
||||
if data := merge(list); data != nil {
|
||||
// Merge success!
|
||||
mergedTargets[target] = *data
|
||||
continue
|
||||
}
|
||||
}
|
||||
maybeMergeable = false // Merge of this node is not possible
|
||||
}
|
||||
|
||||
// Remove all children that have been merged
|
||||
for key := range n.Children {
|
||||
if _, merged := mergedTargets[key.Target]; merged {
|
||||
delete(n.Children, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Add wildcards for merged targets
|
||||
for target, data := range mergedTargets {
|
||||
data := data // Don't take address of iterator
|
||||
n.getOrCreateChild(TreeNodeChildKey{"*", target}).Data = &data
|
||||
}
|
||||
|
||||
// If any of the targets are unmergeable, then we cannot merge the node itself.
|
||||
if !maybeMergeable {
|
||||
return nil
|
||||
}
|
||||
|
||||
// All targets were merged. Attempt to merge each of the targets.
|
||||
data := make([]Data, 0, len(mergedTargets))
|
||||
for _, d := range mergedTargets {
|
||||
data = append(data, d)
|
||||
}
|
||||
return merge(data)
|
||||
}
|
||||
|
||||
// print writes a textual representation of this node and its children to w.
|
||||
// prefix is used as the line prefix for each node, which is appended with
|
||||
// whitespace for each child node.
|
||||
func (n *TreeNode[Data]) print(w io.Writer, prefix string) {
|
||||
fmt.Fprintf(w, "%v{\n", prefix)
|
||||
fmt.Fprintf(w, "%v query: '%v'\n", prefix, n.Query)
|
||||
fmt.Fprintf(w, "%v data: '%v'\n", prefix, n.Data)
|
||||
for _, key := range n.sortedChildKeys() {
|
||||
n.Children[key].print(w, prefix+" ")
|
||||
}
|
||||
fmt.Fprintf(w, "%v}\n", prefix)
|
||||
}
|
||||
|
||||
// Format implements the io.Formatter interface.
|
||||
// See https://pkg.go.dev/fmt#Formatter
|
||||
func (n *TreeNode[Data]) Format(f fmt.State, verb rune) {
|
||||
n.print(f, "")
|
||||
}
|
||||
|
||||
// getOrCreateChild returns the child with the given key if it exists,
|
||||
// otherwise the child node is created and added to n and is returned.
|
||||
func (n *TreeNode[Data]) getOrCreateChild(key TreeNodeChildKey) *TreeNode[Data] {
|
||||
if n.Children == nil {
|
||||
child := &TreeNode[Data]{Query: n.Query.Append(key.Target, key.Name)}
|
||||
n.Children = TreeNodeChildren[Data]{key: child}
|
||||
return child
|
||||
}
|
||||
if child, ok := n.Children[key]; ok {
|
||||
return child
|
||||
}
|
||||
child := &TreeNode[Data]{Query: n.Query.Append(key.Target, key.Name)}
|
||||
n.Children[key] = child
|
||||
return child
|
||||
}
|
||||
|
||||
// QueryData is a pair of a Query and a generic Data type.
|
||||
// Used by NewTree for constructing a tree with entries.
|
||||
type QueryData[Data any] struct {
|
||||
Query Query
|
||||
Data Data
|
||||
}
|
||||
|
||||
// NewTree returns a new Tree populated with the given entries.
|
||||
// If entries returns duplicate queries, then ErrDuplicateData will be returned.
|
||||
func NewTree[Data any](entries ...QueryData[Data]) (Tree[Data], error) {
|
||||
out := Tree[Data]{}
|
||||
for _, qd := range entries {
|
||||
if err := out.Add(qd.Query, qd.Data); err != nil {
|
||||
return Tree[Data]{}, err
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Add adds a new data to the tree.
|
||||
// Returns ErrDuplicateData if the tree already contains a data for the given
|
||||
func (t *Tree[Data]) Add(q Query, d Data) error {
|
||||
node := &t.TreeNode
|
||||
q.Walk(func(q Query, t Target, n string) error {
|
||||
node = node.getOrCreateChild(TreeNodeChildKey{n, t})
|
||||
return nil
|
||||
})
|
||||
if node.Data != nil {
|
||||
return ErrDuplicateData{node.Query}
|
||||
}
|
||||
node.Data = &d
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reduce reduces the tree using the Merger function f.
|
||||
// If the Merger function returns a non-nil Data value, then this will be used
|
||||
// to replace the non-leaf node with a new leaf node holding the returned Data.
|
||||
// This process recurses up to the tree root.
|
||||
func (t *Tree[Data]) Reduce(f Merger[Data]) {
|
||||
for _, root := range t.TreeNode.Children {
|
||||
root.merge(f)
|
||||
}
|
||||
}
|
||||
|
||||
// ReduceUnder reduces the sub-tree under the given query using the Merger
|
||||
// function f.
|
||||
// If the Merger function returns a non-nil Data value, then this will be used
|
||||
// to replace the non-leaf node with a new leaf node holding the returned Data.
|
||||
// This process recurses up to the node pointed at by the query to.
|
||||
func (t *Tree[Data]) ReduceUnder(to Query, f Merger[Data]) error {
|
||||
node := &t.TreeNode
|
||||
return to.Walk(func(q Query, t Target, n string) error {
|
||||
if n == "*" {
|
||||
node.merge(f)
|
||||
return nil
|
||||
}
|
||||
child, ok := node.Children[TreeNodeChildKey{n, t}]
|
||||
if !ok {
|
||||
return ErrNoDataForQuery{q}
|
||||
}
|
||||
node = child
|
||||
if q == to {
|
||||
node.merge(f)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// glob calls f for every node under the given query.
|
||||
func (t *Tree[Data]) glob(fq Query, f func(f *TreeNode[Data]) error) error {
|
||||
node := &t.TreeNode
|
||||
return fq.Walk(func(q Query, t Target, n string) error {
|
||||
if n == "*" {
|
||||
// Wildcard reached.
|
||||
// Glob the parent, but restrict to the wildcard target type.
|
||||
for _, key := range node.sortedChildKeys() {
|
||||
child := node.Children[key]
|
||||
if child.Query.Target() == t {
|
||||
if err := child.traverse(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
switch t {
|
||||
case Suite, Files, Tests:
|
||||
child, ok := node.Children[TreeNodeChildKey{n, t}]
|
||||
if !ok {
|
||||
return ErrNoDataForQuery{q}
|
||||
}
|
||||
node = child
|
||||
case Cases:
|
||||
for _, key := range node.sortedChildKeys() {
|
||||
child := node.Children[key]
|
||||
if child.Query.Contains(fq) {
|
||||
if err := f(child); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if q == fq {
|
||||
return node.traverse(f)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Replace replaces the sub-tree matching the query 'what' with the Data 'with'
|
||||
func (t *Tree[Data]) Replace(what Query, with Data) error {
|
||||
node := &t.TreeNode
|
||||
return what.Walk(func(q Query, t Target, n string) error {
|
||||
childKey := TreeNodeChildKey{n, t}
|
||||
if q == what {
|
||||
for key, child := range node.Children {
|
||||
// Use Query.Contains() to handle matching of Cases
|
||||
// (which are not split into tree nodes)
|
||||
if q.Contains(child.Query) {
|
||||
delete(node.Children, key)
|
||||
}
|
||||
}
|
||||
node = node.getOrCreateChild(childKey)
|
||||
node.Data = &with
|
||||
} else {
|
||||
child, ok := node.Children[childKey]
|
||||
if !ok {
|
||||
return ErrNoDataForQuery{q}
|
||||
}
|
||||
node = child
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// List returns the tree nodes flattened as a list of QueryData
|
||||
func (t *Tree[Data]) List() []QueryData[Data] {
|
||||
out := []QueryData[Data]{}
|
||||
t.traverse(func(n *TreeNode[Data]) error {
|
||||
if n.Data != nil {
|
||||
out = append(out, QueryData[Data]{n.Query, *n.Data})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
// Glob returns a list of QueryData's for every node that is under the given
|
||||
// query, which holds data.
|
||||
// Glob handles wildcards as well as non-wildcard queries:
|
||||
// * A non-wildcard query will match the node itself, along with every node
|
||||
// under the query. For example: 'a:b' will match every File and Test
|
||||
// node under 'a:b', including 'a:b' itself.
|
||||
// * A wildcard Query will include every node under the parent node with the
|
||||
// matching Query target. For example: 'a:b:*' will match every Test
|
||||
// node (excluding File nodes) under 'a:b', 'a:b' will not be included.
|
||||
func (t *Tree[Data]) Glob(q Query) ([]QueryData[Data], error) {
|
||||
out := []QueryData[Data]{}
|
||||
err := t.glob(q, func(n *TreeNode[Data]) error {
|
||||
if n.Data != nil {
|
||||
out = append(out, QueryData[Data]{n.Query, *n.Data})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
|
@ -0,0 +1,934 @@
|
|||
package query_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"dawn.googlesource.com/dawn/tools/src/container"
|
||||
"dawn.googlesource.com/dawn/tools/src/cts/query"
|
||||
"dawn.googlesource.com/dawn/tools/src/utils"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
var (
|
||||
abort = "Abort"
|
||||
crash = "Crash"
|
||||
failure = "Failure"
|
||||
pass = "Pass"
|
||||
skip = "Skip"
|
||||
)
|
||||
|
||||
func NewTree[Data any](t *testing.T, entries ...query.QueryData[Data]) (query.Tree[Data], error) {
|
||||
return query.NewTree(entries...)
|
||||
}
|
||||
|
||||
func TestNewSingle(t *testing.T) {
|
||||
type Tree = query.Tree[string]
|
||||
type Node = query.TreeNode[string]
|
||||
type QueryData = query.QueryData[string]
|
||||
type Children = query.TreeNodeChildren[string]
|
||||
|
||||
type Test struct {
|
||||
in QueryData
|
||||
expect Tree
|
||||
}
|
||||
for _, test := range []Test{
|
||||
{ /////////////////////////////////////////////////////////////////////
|
||||
in: QueryData{
|
||||
Query: Q(`suite:*`),
|
||||
Data: pass,
|
||||
},
|
||||
expect: Tree{
|
||||
TreeNode: Node{
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`suite`, query.Suite}: {
|
||||
Query: Q(`suite`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`*`, query.Files}: {
|
||||
Query: Q(`suite:*`),
|
||||
Data: &pass,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ /////////////////////////////////////////////////////////////////////
|
||||
in: QueryData{
|
||||
Query: Q(`suite:a,*`),
|
||||
Data: pass,
|
||||
},
|
||||
expect: Tree{
|
||||
TreeNode: Node{
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`suite`, query.Suite}: {
|
||||
Query: Q(`suite`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`a`, query.Files}: {
|
||||
Query: Q(`suite:a`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`*`, query.Files}: {
|
||||
Query: Q(`suite:a,*`),
|
||||
Data: &pass,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ /////////////////////////////////////////////////////////////////////
|
||||
in: QueryData{
|
||||
Query: Q(`suite:a,b:*`),
|
||||
Data: pass,
|
||||
},
|
||||
expect: Tree{
|
||||
TreeNode: Node{
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`suite`, query.Suite}: {
|
||||
Query: Q(`suite`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`a`, query.Files}: {
|
||||
Query: Q(`suite:a`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`b`, query.Files}: {
|
||||
Query: Q(`suite:a,b`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`*`, query.Tests}: {
|
||||
Query: Q(`suite:a,b:*`),
|
||||
Data: &pass,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ /////////////////////////////////////////////////////////////////////
|
||||
in: QueryData{
|
||||
Query: Q(`suite:a,b:c:*`),
|
||||
Data: pass,
|
||||
},
|
||||
expect: Tree{
|
||||
TreeNode: Node{
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`suite`, query.Suite}: {
|
||||
Query: Q(`suite`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`a`, query.Files}: {
|
||||
Query: Q(`suite:a`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`b`, query.Files}: {
|
||||
Query: Q(`suite:a,b`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`c`, query.Tests}: {
|
||||
Query: Q(`suite:a,b:c`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`*`, query.Cases}: {
|
||||
Query: Q(`suite:a,b:c:*`),
|
||||
Data: &pass,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ /////////////////////////////////////////////////////////////////////
|
||||
in: QueryData{
|
||||
Query: Q(`suite:a,b,c:d,e:f="g";h=[1,2,3];i=4;*`),
|
||||
Data: pass,
|
||||
},
|
||||
expect: Tree{
|
||||
TreeNode: Node{
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`suite`, query.Suite}: {
|
||||
Query: Q(`suite`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`a`, query.Files}: {
|
||||
Query: Q(`suite:a`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`b`, query.Files}: {
|
||||
Query: Q(`suite:a,b`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`c`, query.Files}: {
|
||||
Query: Q(`suite:a,b,c`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`d`, query.Tests}: {
|
||||
Query: Q(`suite:a,b,c:d`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`e`, query.Tests}: {
|
||||
Query: Q(`suite:a,b,c:d,e`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`f="g";h=[1,2,3];i=4;*`, query.Cases}: {
|
||||
Query: Q(`suite:a,b,c:d,e:f="g";h=[1,2,3];i=4;*`),
|
||||
Data: &pass,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ /////////////////////////////////////////////////////////////////////
|
||||
in: QueryData{
|
||||
Query: Q(`suite:a,b:c:d="e";*`), Data: pass,
|
||||
},
|
||||
expect: Tree{
|
||||
TreeNode: Node{
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`suite`, query.Suite}: {
|
||||
Query: Q(`suite`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`a`, query.Files}: {
|
||||
Query: Q(`suite:a`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`b`, query.Files}: {
|
||||
Query: Q(`suite:a,b`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`c`, query.Tests}: {
|
||||
Query: Q(`suite:a,b:c`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`d="e";*`, query.Cases}: {
|
||||
Query: Q(`suite:a,b:c:d="e";*`),
|
||||
Data: &pass,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
got, err := NewTree(t, test.in)
|
||||
if err != nil {
|
||||
t.Errorf("NewTree(%v): %v", test.in, err)
|
||||
continue
|
||||
}
|
||||
if diff := cmp.Diff(got, test.expect); diff != "" {
|
||||
t.Errorf("NewTree(%v) tree was not as expected:\n%v", test.in, diff)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNewMultiple(t *testing.T) {
|
||||
type Tree = query.Tree[string]
|
||||
type Node = query.TreeNode[string]
|
||||
type QueryData = query.QueryData[string]
|
||||
type Children = query.TreeNodeChildren[string]
|
||||
|
||||
got, err := NewTree(t,
|
||||
QueryData{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:h,b:c:f="g";*`), Data: abort},
|
||||
QueryData{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewTree() returned %v", err)
|
||||
}
|
||||
|
||||
expect := Tree{
|
||||
TreeNode: Node{
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`suite`, query.Suite}: {
|
||||
Query: Q(`suite`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`a`, query.Files}: {
|
||||
Query: Q(`suite:a`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`b`, query.Files}: {
|
||||
Query: Q(`suite:a,b`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`c`, query.Tests}: {
|
||||
Query: Q(`suite:a,b:c`),
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`d="e";*`, query.Cases}: {
|
||||
Query: Q(`suite:a,b:c:d="e";*`),
|
||||
Data: &failure,
|
||||
},
|
||||
query.TreeNodeChildKey{`f="g";*`, query.Cases}: {
|
||||
Query: Q(`suite:a,b:c:f="g";*`),
|
||||
Data: &skip,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
query.TreeNodeChildKey{`h`, query.Files}: {
|
||||
Query: query.Query{
|
||||
Suite: `suite`,
|
||||
Files: `h`,
|
||||
},
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`b`, query.Files}: {
|
||||
Query: query.Query{
|
||||
Suite: `suite`,
|
||||
Files: `h,b`,
|
||||
},
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`c`, query.Tests}: {
|
||||
Query: query.Query{
|
||||
Suite: `suite`,
|
||||
Files: `h,b`,
|
||||
Tests: `c`,
|
||||
},
|
||||
Children: Children{
|
||||
query.TreeNodeChildKey{`f="g";*`, query.Cases}: {
|
||||
Query: query.Query{
|
||||
Suite: `suite`,
|
||||
Files: `h,b`,
|
||||
Tests: `c`,
|
||||
Cases: `f="g";*`,
|
||||
},
|
||||
Data: &abort,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if diff := cmp.Diff(got, expect); diff != "" {
|
||||
t.Errorf("NewTree() was not as expected:\n%v", diff)
|
||||
t.Errorf("got:\n%v", got)
|
||||
t.Errorf("expect:\n%v", expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWithCollision(t *testing.T) {
|
||||
type Tree = query.Tree[string]
|
||||
type QueryData = query.QueryData[string]
|
||||
|
||||
got, err := NewTree(t,
|
||||
QueryData{Query: Q(`suite:a,b:c:*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:a,b:c:*`), Data: skip},
|
||||
)
|
||||
expect := Tree{}
|
||||
expectErr := query.ErrDuplicateData{
|
||||
Query: Q(`suite:a,b:c:*`),
|
||||
}
|
||||
if diff := cmp.Diff(err, expectErr); diff != "" {
|
||||
t.Errorf("NewTree() error was not as expected:\n%v", diff)
|
||||
}
|
||||
if diff := cmp.Diff(got, expect); diff != "" {
|
||||
t.Errorf("NewTree() was not as expected:\n%v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
type QueryData = query.QueryData[string]
|
||||
|
||||
tree, err := NewTree(t,
|
||||
QueryData{Query: Q(`suite:*`), Data: skip},
|
||||
QueryData{Query: Q(`suite:a,*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:a,b:c:*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:a,b:c:d;*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:h,b:c:f="g";*`), Data: abort},
|
||||
QueryData{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewTree() returned %v", err)
|
||||
}
|
||||
|
||||
got := tree.List()
|
||||
expect := []QueryData{
|
||||
{Query: Q(`suite:*`), Data: skip},
|
||||
{Query: Q(`suite:a,*`), Data: failure},
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d;*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
{Query: Q(`suite:h,b:c:f="g";*`), Data: abort},
|
||||
}
|
||||
if diff := cmp.Diff(got, expect); diff != "" {
|
||||
t.Errorf("List() was not as expected:\n%v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
// reducer is used by Reduce() and ReduceUnder() tests for reducing the tree.
|
||||
// reducer returns a pointer to the common string if all strings in data are
|
||||
// equal, otherwise returns nil
|
||||
func reducer(data []string) *string {
|
||||
if s := container.NewSet(data...); len(s) == 1 {
|
||||
item := s.One()
|
||||
return &item
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestReduce(t *testing.T) {
|
||||
type QueryData = query.QueryData[string]
|
||||
|
||||
type Test struct {
|
||||
name string
|
||||
in []QueryData
|
||||
expect []QueryData
|
||||
}
|
||||
for _, test := range []Test{
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Different file results - A",
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Different file results - B",
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
{Query: Q(`suite:a,d,*`), Data: skip},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
{Query: Q(`suite:a,d,*`), Data: skip},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Different test results",
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a,b:*`), Data: failure},
|
||||
{Query: Q(`suite:a,c:*`), Data: pass},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b:*`), Data: failure},
|
||||
{Query: Q(`suite:a,c:*`), Data: pass},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Same file results",
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,c,*`), Data: failure},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:*`), Data: failure},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Same test results",
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a,b:*`), Data: failure},
|
||||
{Query: Q(`suite:a,c:*`), Data: failure},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:*`), Data: failure},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "File vs test",
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a:b,c*`), Data: failure},
|
||||
{Query: Q(`suite:a,b,c*`), Data: pass},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,*`), Data: pass},
|
||||
{Query: Q(`suite:a:*`), Data: failure},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Sibling cases, no reduce",
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a:b:c;d=e;f=g;*`), Data: failure},
|
||||
{Query: Q(`suite:a:b:c;d=e;f=h;*`), Data: pass},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a:b:c;d=e;f=g;*`), Data: failure},
|
||||
{Query: Q(`suite:a:b:c;d=e;f=h;*`), Data: pass},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Sibling cases, reduce to test",
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a:b:c=1;d="x";*`), Data: failure},
|
||||
{Query: Q(`suite:a:b:c=1;d="y";*`), Data: failure},
|
||||
{Query: Q(`suite:a:z:*`), Data: pass},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a:b:*`), Data: failure},
|
||||
{Query: Q(`suite:a:z:*`), Data: pass},
|
||||
},
|
||||
},
|
||||
} {
|
||||
tree, err := NewTree(t, test.in...)
|
||||
if err != nil {
|
||||
t.Errorf("Test '%v':\nNewTree() returned %v", test.name, err)
|
||||
continue
|
||||
}
|
||||
tree.Reduce(reducer)
|
||||
results := tree.List()
|
||||
if diff := cmp.Diff(results, test.expect); diff != "" {
|
||||
t.Errorf("Test '%v':\n%v", test.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReduceUnder(t *testing.T) {
|
||||
type QueryData = query.QueryData[string]
|
||||
|
||||
type Test struct {
|
||||
location string
|
||||
to query.Query
|
||||
in []QueryData
|
||||
expect []QueryData
|
||||
expectErr error
|
||||
}
|
||||
for _, test := range []Test{
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
location: utils.ThisLine(),
|
||||
to: Q(`suite:a,b,*`),
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
location: utils.ThisLine(),
|
||||
to: Q(`suite:a,*`),
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,*`), Data: failure},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
location: utils.ThisLine(),
|
||||
to: Q(`suite:*`),
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a,b:*`), Data: failure},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:*`), Data: failure},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
location: utils.ThisLine(),
|
||||
to: Q(`suite:a,*`),
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
location: utils.ThisLine(),
|
||||
to: Q(`suite:a,*`),
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: pass},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,*`), Data: pass},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
location: utils.ThisLine(),
|
||||
to: Q(`suite:a`),
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: pass},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,*`), Data: pass},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
location: utils.ThisLine(),
|
||||
to: Q(`suite:x`),
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: pass},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: pass},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
},
|
||||
expectErr: query.ErrNoDataForQuery{
|
||||
Query: Q(`suite:x`),
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
location: utils.ThisLine(),
|
||||
to: Q(`suite:a,b,c,*`),
|
||||
in: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: pass},
|
||||
},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: pass},
|
||||
},
|
||||
expectErr: query.ErrNoDataForQuery{
|
||||
Query: Q(`suite:a,b,c`),
|
||||
},
|
||||
},
|
||||
} {
|
||||
tree, err := NewTree(t, test.in...)
|
||||
if err != nil {
|
||||
t.Errorf("\n%v NewTree(): %v", test.location, err)
|
||||
continue
|
||||
}
|
||||
err = tree.ReduceUnder(test.to, reducer)
|
||||
if diff := cmp.Diff(err, test.expectErr); diff != "" {
|
||||
t.Errorf("\n%v ReduceUnder(): %v", test.location, err)
|
||||
}
|
||||
results := tree.List()
|
||||
if diff := cmp.Diff(results, test.expect); diff != "" {
|
||||
t.Errorf("\n%v List(): %v", test.location, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace(t *testing.T) {
|
||||
type QueryData = query.QueryData[string]
|
||||
|
||||
type Test struct {
|
||||
name string
|
||||
base []QueryData
|
||||
replacement QueryData
|
||||
expect []QueryData
|
||||
expectErr error
|
||||
}
|
||||
for _, test := range []Test{
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Replace file. Direct",
|
||||
base: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
},
|
||||
replacement: QueryData{Q(`suite:a,b,*`), skip},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: skip},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Replace file. Indirect",
|
||||
base: []QueryData{
|
||||
{Query: Q(`suite:a,b,c,*`), Data: failure},
|
||||
{Query: Q(`suite:a,b,d,*`), Data: pass},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
},
|
||||
replacement: QueryData{Q(`suite:a,b,*`), skip},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: skip},
|
||||
{Query: Q(`suite:a,c,*`), Data: pass},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "File vs Test",
|
||||
base: []QueryData{
|
||||
{Query: Q(`suite:a,b:c,*`), Data: crash},
|
||||
{Query: Q(`suite:a,b:d,*`), Data: abort},
|
||||
{Query: Q(`suite:a,b,c,*`), Data: failure},
|
||||
{Query: Q(`suite:a,b,d,*`), Data: pass},
|
||||
},
|
||||
replacement: QueryData{Q(`suite:a,b,*`), skip},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: skip},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Cases. * with *",
|
||||
base: []QueryData{
|
||||
{Query: Q(`suite:file:test:*`), Data: failure},
|
||||
},
|
||||
replacement: QueryData{Q(`suite:file:test:*`), pass},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:file:test:*`), Data: pass},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Cases. Mixed with *",
|
||||
base: []QueryData{
|
||||
{Query: Q(`suite:file:test:a=1,*`), Data: failure},
|
||||
{Query: Q(`suite:file:test:a=2,*`), Data: skip},
|
||||
{Query: Q(`suite:file:test:a=3,*`), Data: crash},
|
||||
},
|
||||
replacement: QueryData{Q(`suite:file:test:*`), pass},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:file:test:*`), Data: pass},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Cases. Replace partial - (a=1)",
|
||||
base: []QueryData{
|
||||
{Query: Q(`suite:file:test:a=1;b=x;*`), Data: failure},
|
||||
{Query: Q(`suite:file:test:a=1;b=y;*`), Data: failure},
|
||||
{Query: Q(`suite:file:test:a=2;b=y;*`), Data: failure},
|
||||
},
|
||||
replacement: QueryData{Q(`suite:file:test:a=1;*`), pass},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:file:test:a=1;*`), Data: pass},
|
||||
{Query: Q(`suite:file:test:a=2;b=y;*`), Data: failure},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Cases. Replace partial - (b=y)",
|
||||
base: []QueryData{
|
||||
{Query: Q(`suite:file:test:a=1;b=x;*`), Data: failure},
|
||||
{Query: Q(`suite:file:test:a=1;b=y;*`), Data: failure},
|
||||
{Query: Q(`suite:file:test:a=2;b=y;*`), Data: failure},
|
||||
},
|
||||
replacement: QueryData{Q(`suite:file:test:b=y;*`), pass},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:file:test:a=1;b=x;*`), Data: failure},
|
||||
{Query: Q(`suite:file:test:b=y;*`), Data: pass},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Error. No data for query - short",
|
||||
base: []QueryData{
|
||||
{Query: Q(`suite:file:test:a=1;b=x;*`), Data: failure},
|
||||
},
|
||||
replacement: QueryData{Q(`suite:missing:*`), pass},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:file:test:a=1;b=x;*`), Data: failure},
|
||||
},
|
||||
expectErr: query.ErrNoDataForQuery{Q(`suite:missing`)},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
name: "Error. No data for query - long",
|
||||
base: []QueryData{
|
||||
{Query: Q(`suite:file:test:*`), Data: failure},
|
||||
},
|
||||
replacement: QueryData{Q(`suite:file:test,missing,*`), pass},
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:file:test:*`), Data: failure},
|
||||
},
|
||||
expectErr: query.ErrNoDataForQuery{Q(`suite:file:test,missing`)},
|
||||
},
|
||||
} {
|
||||
tree, err := NewTree(t, test.base...)
|
||||
if err != nil {
|
||||
t.Errorf("Test '%v':\nNewTree(): %v", test.name, err)
|
||||
continue
|
||||
}
|
||||
err = tree.Replace(test.replacement.Query, test.replacement.Data)
|
||||
if diff := cmp.Diff(err, test.expectErr); diff != "" {
|
||||
t.Errorf("Test '%v':\nReplace() error: %v", test.name, err)
|
||||
continue
|
||||
}
|
||||
if diff := cmp.Diff(tree.List(), test.expect); diff != "" {
|
||||
t.Errorf("Test '%v':\n%v", test.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlob(t *testing.T) {
|
||||
type QueryData = query.QueryData[string]
|
||||
|
||||
tree, err := NewTree(t,
|
||||
QueryData{Query: Q(`suite:*`), Data: skip},
|
||||
QueryData{Query: Q(`suite:a,*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:a,b:c:d;*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:h,b:c:f="g";*`), Data: abort},
|
||||
QueryData{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
QueryData{Query: Q(`suite:a,b:d:*`), Data: failure},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewTree() returned %v", err)
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
query query.Query
|
||||
expect []QueryData
|
||||
expectErr error
|
||||
}
|
||||
for _, test := range []Test{
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
query: Q(`suite`),
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:*`), Data: skip},
|
||||
{Query: Q(`suite:a,*`), Data: failure},
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d;*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
{Query: Q(`suite:a,b:d:*`), Data: failure},
|
||||
{Query: Q(`suite:h,b:c:f="g";*`), Data: abort},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
query: Q(`suite:*`),
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:*`), Data: skip},
|
||||
{Query: Q(`suite:a,*`), Data: failure},
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d;*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
{Query: Q(`suite:a,b:d:*`), Data: failure},
|
||||
{Query: Q(`suite:h,b:c:f="g";*`), Data: abort},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
query: Q(`suite:a`),
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,*`), Data: failure},
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d;*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
{Query: Q(`suite:a,b:d:*`), Data: failure},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
query: Q(`suite:a,*`),
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,*`), Data: failure},
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d;*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
{Query: Q(`suite:a,b:d:*`), Data: failure},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
query: Q(`suite:a,b`),
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d;*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
{Query: Q(`suite:a,b:d:*`), Data: failure},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
query: Q(`suite:a,b,*`),
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
query: Q(`suite:a,b:c:*`),
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b:c:d;*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
query: Q(`suite:a,b:c`),
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b:c:d;*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
query: Q(`suite:a,b:c:d="e";*`),
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
query: Q(`suite:a,b:c:d;*`),
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b:c:d;*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
query: Q(`suite:a,b:c:f="g";*`),
|
||||
expect: []QueryData{
|
||||
{Query: Q(`suite:a,b:c:d;*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
query: Q(`suite:x,y`),
|
||||
expectErr: query.ErrNoDataForQuery{Q(`suite:x`)},
|
||||
},
|
||||
{ //////////////////////////////////////////////////////////////////////
|
||||
query: Q(`suite:a,b:x`),
|
||||
expectErr: query.ErrNoDataForQuery{Q(`suite:a,b:x`)},
|
||||
},
|
||||
} {
|
||||
got, err := tree.Glob(test.query)
|
||||
if diff := cmp.Diff(err, test.expectErr); diff != "" {
|
||||
t.Errorf("Glob('%v') error: %v", test.query, err)
|
||||
continue
|
||||
}
|
||||
if diff := cmp.Diff(got, test.expect); diff != "" {
|
||||
t.Errorf("Glob('%v'):\n%v", test.query, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat(t *testing.T) {
|
||||
type QueryData = query.QueryData[string]
|
||||
|
||||
tree, err := NewTree(t,
|
||||
QueryData{Query: Q(`suite:*`), Data: skip},
|
||||
QueryData{Query: Q(`suite:a,*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:a,b,*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:a,b:c:d;*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:a,b:c:d="e";*`), Data: failure},
|
||||
QueryData{Query: Q(`suite:h,b:c:f="g";*`), Data: abort},
|
||||
QueryData{Query: Q(`suite:a,b:c:f="g";*`), Data: skip},
|
||||
QueryData{Query: Q(`suite:a,b:d:*`), Data: failure},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewTree() returned %v", err)
|
||||
}
|
||||
|
||||
callA := fmt.Sprint(tree)
|
||||
callB := fmt.Sprint(tree)
|
||||
|
||||
if diff := cmp.Diff(callA, callB); diff != "" {
|
||||
t.Errorf("Format():\n%v", diff)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue