tools: Add src/cts/query

Provides a type to represent the CTS query strings.

100% test coverage.

Bug: dawn:1342

Change-Id: I3769b094ba64221a7b79dd38f76daf0125ee9e28
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/85221
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
Ben Clayton 2022-04-01 16:31:15 +00:00 committed by Dawn LUCI CQ
parent 9c53d8a02c
commit 75cc17f2d1
2 changed files with 1227 additions and 0 deletions

View File

@ -0,0 +1,362 @@
// 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 provides helpers for parsing and mutating WebGPU CTS queries.
//
// The full query syntax is described at:
// https://github.com/gpuweb/cts/blob/main/docs/terms.md#queries
//
// Note that this package supports a superset of the official CTS query syntax,
// as this package permits parsing and printing of queries that do not end in a
// wildcard, whereas the CTS requires that all queries end in wildcards unless
// they identify a specific test.
// For example, the following queries are considered valid by this package, but
// would be rejected by the CTS:
// `suite`, `suite:file`, `suite:file,file`, `suite:file,file:test`.
//
// This relaxation is intentional as the Query type is used for constructing and
// reducing query trees, and always requiring a wildcard adds unnecessary
// complexity.
package query
import (
"fmt"
"strings"
)
// Query represents a WebGPU test query
// Example queries:
// 'suite'
// 'suite:*'
// 'suite:file'
// 'suite:file,*'
// 'suite:file,file'
// 'suite:file,file,*'
// 'suite:file,file,file:test'
// 'suite:file,file,file:test:*'
// 'suite:file,file,file:test,test:case;*'
type Query struct {
Suite string
Files string
Tests string
Cases string
}
// Target is the target of a query, either a Suite, File, Test or Case.
type Target int
// Enumerators of Target
const (
// The query targets a suite
Suite Target = iota
// The query targets one or more files
Files
// The query targets one or more tests
Tests
// The query targets one or more test cases
Cases
TargetCount
)
// Format writes the Target to the fmt.State
func (l Target) Format(f fmt.State, verb rune) {
switch l {
case Suite:
fmt.Fprint(f, "suite")
case Files:
fmt.Fprint(f, "files")
case Tests:
fmt.Fprint(f, "tests")
case Cases:
fmt.Fprint(f, "cases")
default:
fmt.Fprint(f, "<invalid>")
}
}
// Delimiter constants used by the query format
const (
TargetDelimiter = ":"
FileDelimiter = ","
TestDelimiter = ","
CaseDelimiter = ";"
)
// Parse parses a query string
func Parse(s string) (Query, error) {
parts := strings.Split(s, TargetDelimiter)
q := Query{}
switch len(parts) {
default:
q.Cases = strings.Join(parts[3:], TargetDelimiter)
fallthrough
case 3:
q.Tests = parts[2]
fallthrough
case 2:
q.Files = parts[1]
fallthrough
case 1:
q.Suite = parts[0]
}
return q, nil
}
// AppendFiles returns a new query with the strings appended to the 'files'
func (q Query) AppendFiles(f ...string) Query {
if len(f) > 0 {
if q.Files == "" {
q.Files = strings.Join(f, FileDelimiter)
} else {
q.Files = q.Files + FileDelimiter + strings.Join(f, FileDelimiter)
}
}
return q
}
// SplitFiles returns the separated 'files' part of the query
func (q Query) SplitFiles() []string {
if q.Files != "" {
return strings.Split(q.Files, FileDelimiter)
}
return nil
}
// AppendTests returns a new query with the strings appended to the 'tests'
func (q Query) AppendTests(t ...string) Query {
if len(t) > 0 {
if q.Tests == "" {
q.Tests = strings.Join(t, TestDelimiter)
} else {
q.Tests = q.Tests + TestDelimiter + strings.Join(t, TestDelimiter)
}
}
return q
}
// SplitTests returns the separated 'tests' part of the query
func (q Query) SplitTests() []string {
if q.Tests != "" {
return strings.Split(q.Tests, TestDelimiter)
}
return nil
}
// AppendCases returns a new query with the strings appended to the 'cases'
func (q Query) AppendCases(c ...string) Query {
if len(c) > 0 {
if q.Cases == "" {
q.Cases = strings.Join(c, CaseDelimiter)
} else {
q.Cases = q.Cases + CaseDelimiter + strings.Join(c, CaseDelimiter)
}
}
return q
}
// SplitCases returns the separated 'cases' part of the query
func (q Query) SplitCases() []string {
if q.Cases != "" {
return strings.Split(q.Cases, CaseDelimiter)
}
return nil
}
// Case parameters is a map of parameter name to parameter value
type CaseParameters map[string]string
// CaseParameters returns all the case parameters of the query
func (q Query) CaseParameters() CaseParameters {
if q.Cases != "" {
out := CaseParameters{}
for _, c := range strings.Split(q.Cases, CaseDelimiter) {
idx := strings.IndexRune(c, '=')
if idx < 0 {
out[c] = ""
} else {
k, v := c[:idx], c[idx+1:]
out[k] = v
}
}
return out
}
return nil
}
// Append returns the query with the additional strings appended to the target
func (q Query) Append(t Target, n ...string) Query {
switch t {
case Files:
return q.AppendFiles(n...)
case Tests:
return q.AppendTests(n...)
case Cases:
return q.AppendCases(n...)
}
panic("invalid target")
}
// Target returns the target of the query
func (q Query) Target() Target {
if q.Files != "" {
if q.Tests != "" {
if q.Cases != "" {
return Cases
}
return Tests
}
return Files
}
return Suite
}
// IsWildcard returns true if the query ends with a wildcard
func (q Query) IsWildcard() bool {
switch q.Target() {
case Suite:
return q.Suite == "*"
case Files:
return strings.HasSuffix(q.Files, "*")
case Tests:
return strings.HasSuffix(q.Tests, "*")
case Cases:
return strings.HasSuffix(q.Cases, "*")
}
panic("invalid target")
}
// String returns the query formatted as a string
func (q Query) String() string {
sb := strings.Builder{}
sb.WriteString(q.Suite)
if q.Files != "" {
sb.WriteString(TargetDelimiter)
sb.WriteString(q.Files)
if q.Tests != "" {
sb.WriteString(TargetDelimiter)
sb.WriteString(q.Tests)
if q.Cases != "" {
sb.WriteString(TargetDelimiter)
sb.WriteString(q.Cases)
}
}
}
return sb.String()
}
// Compare compares the relative order of q and o, returning:
// -1 if q should come before o
// 1 if q should come after o
// 0 if q and o are identical
func (q Query) Compare(o Query) int {
for _, cmp := range []struct{ a, b string }{
{q.Suite, o.Suite},
{q.Files, o.Files},
{q.Tests, o.Tests},
{q.Cases, o.Cases},
} {
if cmp.a < cmp.b {
return -1
}
if cmp.a > cmp.b {
return 1
}
}
return 0
}
// Contains returns true if q is a superset of o
func (q Query) Contains(o Query) bool {
if q.Suite != o.Suite {
return false
}
{
a, b := q.SplitFiles(), o.SplitFiles()
for i, f := range a {
if f == "*" {
return true
}
if i >= len(b) || b[i] != f {
return false
}
}
if len(a) < len(b) {
return false
}
}
{
a, b := q.SplitTests(), o.SplitTests()
for i, f := range a {
if f == "*" {
return true
}
if i >= len(b) || b[i] != f {
return false
}
}
if len(a) < len(b) {
return false
}
}
{
a, b := q.CaseParameters(), o.CaseParameters()
for key, av := range a {
if bv, found := b[key]; found && av != bv {
return false
}
}
}
return true
}
// Callback function for Query.Walk()
// q is the query for the current segment.
// t is the target of the query q.
// n is the name of the new segment.
type WalkCallback func(q Query, t Target, n string) error
// Walk calls 'f' for each suite, file, test segment, and calls f once for all
// cases. If f returns an error then walking is immediately terminated and the
// error is returned.
func (q Query) Walk(f WalkCallback) error {
p := Query{Suite: q.Suite}
if err := f(p, Suite, q.Suite); err != nil {
return err
}
for _, file := range q.SplitFiles() {
p = p.AppendFiles(file)
if err := f(p, Files, file); err != nil {
return err
}
}
for _, test := range q.SplitTests() {
p = p.AppendTests(test)
if err := f(p, Tests, test); err != nil {
return err
}
}
if q.Cases != "" {
if err := f(q, Cases, q.Cases); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,865 @@
// 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_test
import (
"fmt"
"strings"
"testing"
"dawn.googlesource.com/dawn/tools/src/cts/query"
"github.com/google/go-cmp/cmp"
)
func Q(name string) query.Query {
q, err := query.Parse(name)
if err != nil {
panic(err)
}
return q
}
func TestTargetFormat(t *testing.T) {
type Test struct {
target query.Target
expect string
}
for _, test := range []Test{
{query.Suite, "suite"},
{query.Files, "files"},
{query.Tests, "tests"},
{query.Cases, "cases"},
{query.Target(-1), "<invalid>"},
} {
s := strings.Builder{}
_, err := fmt.Fprint(&s, test.target)
if err != nil {
t.Errorf("Fprint() returned %v", err)
continue
}
if diff := cmp.Diff(s.String(), test.expect); diff != "" {
t.Errorf("Fprint('%v')\n%v", test.target, diff)
}
}
}
func TestAppendFiles(t *testing.T) {
type Test struct {
base query.Query
files []string
expect query.Query
}
for _, test := range []Test{
{Q("suite"), []string{}, Q("suite")},
{Q("suite"), []string{"x"}, Q("suite:x")},
{Q("suite"), []string{"x", "y"}, Q("suite:x,y")},
{Q("suite:a"), []string{}, Q("suite:a")},
{Q("suite:a"), []string{"x"}, Q("suite:a,x")},
{Q("suite:a"), []string{"x", "y"}, Q("suite:a,x,y")},
{Q("suite:a,b"), []string{}, Q("suite:a,b")},
{Q("suite:a,b"), []string{"x"}, Q("suite:a,b,x")},
{Q("suite:a,b"), []string{"x", "y"}, Q("suite:a,b,x,y")},
{Q("suite:a,b:c"), []string{}, Q("suite:a,b:c")},
{Q("suite:a,b:c"), []string{"x"}, Q("suite:a,b,x:c")},
{Q("suite:a,b:c"), []string{"x", "y"}, Q("suite:a,b,x,y:c")},
{Q("suite:a,b:c,d"), []string{}, Q("suite:a,b:c,d")},
{Q("suite:a,b:c,d"), []string{"x"}, Q("suite:a,b,x:c,d")},
{Q("suite:a,b:c,d"), []string{"x", "y"}, Q("suite:a,b,x,y:c,d")},
{Q("suite:a,b:c,d:e"), []string{}, Q("suite:a,b:c,d:e")},
{Q("suite:a,b:c,d:e"), []string{"x"}, Q("suite:a,b,x:c,d:e")},
{Q("suite:a,b:c,d:e"), []string{"x", "y"}, Q("suite:a,b,x,y:c,d:e")},
{Q("suite:a,b:c,d:e;f"), []string{}, Q("suite:a,b:c,d:e;f")},
{Q("suite:a,b:c,d:e;f"), []string{"x"}, Q("suite:a,b,x:c,d:e;f")},
{Q("suite:a,b:c,d:e;f"), []string{"x", "y"}, Q("suite:a,b,x,y:c,d:e;f")},
} {
got := test.base.AppendFiles(test.files...)
if diff := cmp.Diff(got, test.expect); diff != "" {
t.Errorf("'%v'.AppendFiles(%v)\n%v", test.base, test.files, diff)
}
}
}
func TestSplitFiles(t *testing.T) {
type Test struct {
query query.Query
expect []string
}
for _, test := range []Test{
{Q("suite"), nil},
{Q("suite:a"), []string{"a"}},
{Q("suite:a,b"), []string{"a", "b"}},
{Q("suite:a,b:c"), []string{"a", "b"}},
{Q("suite:a,b:c,d"), []string{"a", "b"}},
{Q("suite:a,b:c,d:e"), []string{"a", "b"}},
{Q("suite:a,b:c,d:e;f"), []string{"a", "b"}},
} {
got := test.query.SplitFiles()
if diff := cmp.Diff(got, test.expect); diff != "" {
t.Errorf("'%v'.SplitFiles()\n%v", test.query, diff)
}
}
}
func TestAppendTests(t *testing.T) {
type Test struct {
base query.Query
files []string
expect query.Query
}
for _, test := range []Test{
{Q("suite"), []string{}, Q("suite")},
{Q("suite"), []string{"x"}, Q("suite::x")},
{Q("suite"), []string{"x", "y"}, Q("suite::x,y")},
{Q("suite:a"), []string{}, Q("suite:a")},
{Q("suite:a"), []string{"x"}, Q("suite:a:x")},
{Q("suite:a"), []string{"x", "y"}, Q("suite:a:x,y")},
{Q("suite:a,b"), []string{}, Q("suite:a,b")},
{Q("suite:a,b"), []string{"x"}, Q("suite:a,b:x")},
{Q("suite:a,b"), []string{"x", "y"}, Q("suite:a,b:x,y")},
{Q("suite:a,b:c"), []string{}, Q("suite:a,b:c")},
{Q("suite:a,b:c"), []string{"x"}, Q("suite:a,b:c,x")},
{Q("suite:a,b:c"), []string{"x", "y"}, Q("suite:a,b:c,x,y")},
{Q("suite:a,b:c,d"), []string{}, Q("suite:a,b:c,d")},
{Q("suite:a,b:c,d"), []string{"x"}, Q("suite:a,b:c,d,x")},
{Q("suite:a,b:c,d"), []string{"x", "y"}, Q("suite:a,b:c,d,x,y")},
{Q("suite:a,b:c,d:e"), []string{}, Q("suite:a,b:c,d:e")},
{Q("suite:a,b:c,d:e"), []string{"x"}, Q("suite:a,b:c,d,x:e")},
{Q("suite:a,b:c,d:e"), []string{"x", "y"}, Q("suite:a,b:c,d,x,y:e")},
{Q("suite:a,b:c,d:e;f"), []string{}, Q("suite:a,b:c,d:e;f")},
{Q("suite:a,b:c,d:e;f"), []string{"x"}, Q("suite:a,b:c,d,x:e;f")},
{Q("suite:a,b:c,d:e;f"), []string{"x", "y"}, Q("suite:a,b:c,d,x,y:e;f")},
} {
got := test.base.AppendTests(test.files...)
if diff := cmp.Diff(got, test.expect); diff != "" {
t.Errorf("'%v'.AppendTests(%v)\n%v", test.base, test.files, diff)
}
}
}
func TestSplitTests(t *testing.T) {
type Test struct {
query query.Query
tests []string
}
for _, test := range []Test{
{Q("suite"), nil},
{Q("suite:a"), nil},
{Q("suite:a,b"), nil},
{Q("suite:a,b:c"), []string{"c"}},
{Q("suite:a,b:c,d"), []string{"c", "d"}},
{Q("suite:a,b:c,d:e"), []string{"c", "d"}},
{Q("suite:a,b:c,d:e;f"), []string{"c", "d"}},
} {
got := test.query.SplitTests()
if diff := cmp.Diff(got, test.tests); diff != "" {
t.Errorf("'%v'.SplitTests()\n%v", test.query, diff)
}
}
}
func TestAppendCases(t *testing.T) {
type Test struct {
base query.Query
cases []string
expect query.Query
}
for _, test := range []Test{
{Q("suite"), []string{}, Q("suite")},
{Q("suite"), []string{"x"}, Q("suite:::x")},
{Q("suite"), []string{"x", "y"}, Q("suite:::x;y")},
{Q("suite:a"), []string{}, Q("suite:a")},
{Q("suite:a"), []string{"x"}, Q("suite:a::x")},
{Q("suite:a"), []string{"x", "y"}, Q("suite:a::x;y")},
{Q("suite:a,b"), []string{}, Q("suite:a,b")},
{Q("suite:a,b"), []string{"x"}, Q("suite:a,b::x")},
{Q("suite:a,b"), []string{"x", "y"}, Q("suite:a,b::x;y")},
{Q("suite:a,b:c"), []string{}, Q("suite:a,b:c")},
{Q("suite:a,b:c"), []string{"x"}, Q("suite:a,b:c:x")},
{Q("suite:a,b:c"), []string{"x", "y"}, Q("suite:a,b:c:x;y")},
{Q("suite:a,b:c,d"), []string{}, Q("suite:a,b:c,d")},
{Q("suite:a,b:c,d"), []string{"x"}, Q("suite:a,b:c,d:x")},
{Q("suite:a,b:c,d"), []string{"x", "y"}, Q("suite:a,b:c,d:x;y")},
{Q("suite:a,b:c,d:e"), []string{}, Q("suite:a,b:c,d:e")},
{Q("suite:a,b:c,d:e"), []string{"x"}, Q("suite:a,b:c,d:e;x")},
{Q("suite:a,b:c,d:e"), []string{"x", "y"}, Q("suite:a,b:c,d:e;x;y")},
{Q("suite:a,b:c,d:e;f"), []string{}, Q("suite:a,b:c,d:e;f")},
{Q("suite:a,b:c,d:e;f"), []string{"x"}, Q("suite:a,b:c,d:e;f;x")},
{Q("suite:a,b:c,d:e;f"), []string{"x", "y"}, Q("suite:a,b:c,d:e;f;x;y")},
} {
got := test.base.AppendCases(test.cases...)
if diff := cmp.Diff(got, test.expect); diff != "" {
t.Errorf("'%v'.AppendCases(%v)\n%v", test.base, test.cases, diff)
}
}
}
func TestAppend(t *testing.T) {
type Subtest struct {
target query.Target
expect query.Query
}
type Test struct {
base query.Query
strings []string
subtest []Subtest
}
for _, test := range []Test{
{
Q("suite"), []string{}, []Subtest{
{query.Files, Q("suite")},
{query.Tests, Q("suite")},
{query.Cases, Q("suite")},
},
}, {
Q("suite"), []string{"x"}, []Subtest{
{query.Files, Q("suite:x")},
{query.Tests, Q("suite::x")},
{query.Cases, Q("suite:::x")},
},
}, {
Q("suite"), []string{"x", "y"}, []Subtest{
{query.Files, Q("suite:x,y")},
{query.Tests, Q("suite::x,y")},
{query.Cases, Q("suite:::x;y")},
},
}, {
Q("suite:a"), []string{}, []Subtest{
{query.Files, Q("suite:a")},
{query.Tests, Q("suite:a")},
{query.Cases, Q("suite:a")},
},
}, {
Q("suite:a"), []string{"x"}, []Subtest{
{query.Files, Q("suite:a,x")},
{query.Tests, Q("suite:a:x")},
{query.Cases, Q("suite:a::x")},
},
}, {
Q("suite:a"), []string{"x", "y"}, []Subtest{
{query.Files, Q("suite:a,x,y")},
{query.Tests, Q("suite:a:x,y")},
{query.Cases, Q("suite:a::x;y")},
},
}, {
Q("suite:a,b"), []string{}, []Subtest{
{query.Files, Q("suite:a,b")},
{query.Tests, Q("suite:a,b")},
{query.Cases, Q("suite:a,b")},
},
}, {
Q("suite:a,b"), []string{"x"}, []Subtest{
{query.Files, Q("suite:a,b,x")},
{query.Tests, Q("suite:a,b:x")},
{query.Cases, Q("suite:a,b::x")},
},
}, {
Q("suite:a,b"), []string{"x", "y"}, []Subtest{
{query.Files, Q("suite:a,b,x,y")},
{query.Tests, Q("suite:a,b:x,y")},
{query.Cases, Q("suite:a,b::x;y")},
},
}, {
Q("suite:a,b:c"), []string{}, []Subtest{
{query.Files, Q("suite:a,b:c")},
{query.Tests, Q("suite:a,b:c")},
{query.Cases, Q("suite:a,b:c")},
},
}, {
Q("suite:a,b:c"), []string{"x"}, []Subtest{
{query.Files, Q("suite:a,b,x:c")},
{query.Tests, Q("suite:a,b:c,x")},
{query.Cases, Q("suite:a,b:c:x")},
},
}, {
Q("suite:a,b:c"), []string{"x", "y"}, []Subtest{
{query.Files, Q("suite:a,b,x,y:c")},
{query.Tests, Q("suite:a,b:c,x,y")},
{query.Cases, Q("suite:a,b:c:x;y")},
},
}, {
Q("suite:a,b:c,d"), []string{}, []Subtest{
{query.Files, Q("suite:a,b:c,d")},
{query.Tests, Q("suite:a,b:c,d")},
{query.Cases, Q("suite:a,b:c,d")},
},
}, {
Q("suite:a,b:c,d"), []string{"x"}, []Subtest{
{query.Files, Q("suite:a,b,x:c,d")},
{query.Tests, Q("suite:a,b:c,d,x")},
{query.Cases, Q("suite:a,b:c,d:x")},
},
}, {
Q("suite:a,b:c,d"), []string{"x", "y"}, []Subtest{
{query.Files, Q("suite:a,b,x,y:c,d")},
{query.Tests, Q("suite:a,b:c,d,x,y")},
{query.Cases, Q("suite:a,b:c,d:x;y")},
},
}, {
Q("suite:a,b:c,d:e"), []string{}, []Subtest{
{query.Files, Q("suite:a,b:c,d:e")},
{query.Tests, Q("suite:a,b:c,d:e")},
{query.Cases, Q("suite:a,b:c,d:e")},
},
}, {
Q("suite:a,b:c,d:e"), []string{"x"}, []Subtest{
{query.Files, Q("suite:a,b,x:c,d:e")},
{query.Tests, Q("suite:a,b:c,d,x:e")},
{query.Cases, Q("suite:a,b:c,d:e;x")},
},
}, {
Q("suite:a,b:c,d:e"), []string{"x", "y"}, []Subtest{
{query.Files, Q("suite:a,b,x,y:c,d:e")},
{query.Tests, Q("suite:a,b:c,d,x,y:e")},
{query.Cases, Q("suite:a,b:c,d:e;x;y")},
},
}, {
Q("suite:a,b:c,d:e;f"), []string{}, []Subtest{
{query.Files, Q("suite:a,b:c,d:e;f")},
{query.Tests, Q("suite:a,b:c,d:e;f")},
{query.Cases, Q("suite:a,b:c,d:e;f")},
},
}, {
Q("suite:a,b:c,d:e;f"), []string{"x"}, []Subtest{
{query.Files, Q("suite:a,b,x:c,d:e;f")},
{query.Tests, Q("suite:a,b:c,d,x:e;f")},
{query.Cases, Q("suite:a,b:c,d:e;f;x")},
},
}, {
Q("suite:a,b:c,d:e;f"), []string{"x", "y"}, []Subtest{
{query.Files, Q("suite:a,b,x,y:c,d:e;f")},
{query.Tests, Q("suite:a,b:c,d,x,y:e;f")},
{query.Cases, Q("suite:a,b:c,d:e;f;x;y")},
},
},
} {
for _, subtest := range test.subtest {
got := test.base.Append(subtest.target, test.strings...)
if diff := cmp.Diff(got, subtest.expect); diff != "" {
t.Errorf("'%v'.Append(%v, %v)\n%v", test.base, subtest.target, test.base.Files, diff)
}
}
}
}
func TestSplitCases(t *testing.T) {
type Test struct {
query query.Query
expect []string
}
for _, test := range []Test{
{Q("suite"), nil},
{Q("suite:a"), nil},
{Q("suite:a,b"), nil},
{Q("suite:a,b:c"), nil},
{Q("suite:a,b:c,d"), nil},
{Q("suite:a,b:c,d:e"), []string{"e"}},
{Q("suite:a,b:c,d:e;f"), []string{"e", "f"}},
} {
got := test.query.SplitCases()
if diff := cmp.Diff(got, test.expect); diff != "" {
t.Errorf("'%v'.SplitCases()\n%v", test.query, diff)
}
}
}
func TestCaseParameters(t *testing.T) {
type Test struct {
query query.Query
expect query.CaseParameters
}
for _, test := range []Test{
{Q("suite"), nil},
{Q("suite:a"), nil},
{Q("suite:a,b"), nil},
{Q("suite:a,b:c"), nil},
{Q("suite:a,b:c,d"), nil},
{Q("suite:a,b:c,d:e"), query.CaseParameters{"e": ""}},
{Q("suite:a,b:c,d:e;f"), query.CaseParameters{"e": "", "f": ""}},
{Q("suite:a,b:c,d:e=f;g=h"), query.CaseParameters{"e": "f", "g": "h"}},
} {
got := test.query.CaseParameters()
if diff := cmp.Diff(got, test.expect); diff != "" {
t.Errorf("'%v'.CaseParameters()\n%v", test.query, diff)
}
}
}
func TestTarget(t *testing.T) {
type Test struct {
query query.Query
expect query.Target
}
for _, test := range []Test{
{Q("suite"), query.Suite},
{Q("suite:*"), query.Files},
{Q("suite:a"), query.Files},
{Q("suite:a,*"), query.Files},
{Q("suite:a,b"), query.Files},
{Q("suite:a,b:*"), query.Tests},
{Q("suite:a,b:c"), query.Tests},
{Q("suite:a,b:c,*"), query.Tests},
{Q("suite:a,b:c,d"), query.Tests},
{Q("suite:a,b:c,d:*"), query.Cases},
{Q("suite:a,b:c,d:e"), query.Cases},
{Q("suite:a,b:c,d:e;*"), query.Cases},
{Q("suite:a,b:c,d:e;f"), query.Cases},
{Q("suite:a,b:c,d:e;f;*"), query.Cases},
} {
got := test.query.Target()
if diff := cmp.Diff(got, test.expect); diff != "" {
t.Errorf("'%v'.Target()\n%v", test.query, diff)
}
}
}
func TestIsWildcard(t *testing.T) {
type Test struct {
query query.Query
expect bool
}
for _, test := range []Test{
{Q("suite"), false},
{Q("suite:*"), true},
{Q("suite:a"), false},
{Q("suite:a,*"), true},
{Q("suite:a,b"), false},
{Q("suite:a,b:*"), true},
{Q("suite:a,b:c"), false},
{Q("suite:a,b:c,*"), true},
{Q("suite:a,b:c,d"), false},
{Q("suite:a,b:c,d:*"), true},
{Q("suite:a,b:c,d:e"), false},
{Q("suite:a,b:c,d:e;*"), true},
{Q("suite:a,b:c,d:e;f"), false},
{Q("suite:a,b:c,d:e;f;*"), true},
} {
got := test.query.IsWildcard()
if diff := cmp.Diff(got, test.expect); diff != "" {
t.Errorf("'%v'.IsWildcard()\n%v", test.query, diff)
}
}
}
func TestParsePrint(t *testing.T) {
type Test struct {
in string
expect query.Query
}
for _, test := range []Test{
{
"a",
query.Query{
Suite: "a",
},
}, {
"a:*",
query.Query{
Suite: "a",
Files: "*",
},
}, {
"a:b",
query.Query{
Suite: "a",
Files: "b",
},
}, {
"a:b,*",
query.Query{
Suite: "a",
Files: "b,*",
},
}, {
"a:b:*",
query.Query{
Suite: "a",
Files: "b",
Tests: "*",
},
}, {
"a:b,c",
query.Query{
Suite: "a",
Files: "b,c",
},
}, {
"a:b,c:*",
query.Query{
Suite: "a",
Files: "b,c",
Tests: "*",
},
}, {
"a:b,c:d",
query.Query{
Suite: "a",
Files: "b,c",
Tests: "d",
},
}, {
"a:b,c:d,*",
query.Query{
Suite: "a",
Files: "b,c",
Tests: "d,*",
},
}, {
"a:b,c:d,e",
query.Query{
Suite: "a",
Files: "b,c",
Tests: "d,e",
},
}, {
"a:b,c:d,e,*",
query.Query{
Suite: "a",
Files: "b,c",
Tests: "d,e,*",
},
}, {
"a:b,c:d,e:*",
query.Query{
Suite: "a",
Files: "b,c",
Tests: "d,e",
Cases: "*",
},
}, {
"a:b,c:d,e:f=g",
query.Query{
Suite: "a",
Files: "b,c",
Tests: "d,e",
Cases: "f=g",
},
}, {
"a:b,c:d,e:f=g;*",
query.Query{
Suite: "a",
Files: "b,c",
Tests: "d,e",
Cases: "f=g;*",
},
}, {
"a:b,c:d,e:f=g;h=i",
query.Query{
Suite: "a",
Files: "b,c",
Tests: "d,e",
Cases: "f=g;h=i",
},
}, {
"a:b,c:d,e:f=g;h=i;*",
query.Query{
Suite: "a",
Files: "b,c",
Tests: "d,e",
Cases: "f=g;h=i;*",
},
}, {
`a:b,c:d,e:f={"x": 1, "y": 2}`,
query.Query{
Suite: "a",
Files: "b,c",
Tests: "d,e",
Cases: `f={"x": 1, "y": 2}`,
},
},
} {
parsed, err := query.Parse(test.in)
if err != nil {
t.Errorf("query.Parse('%v') returned %v", test.in, err)
continue
}
if diff := cmp.Diff(test.expect, parsed); diff != "" {
t.Errorf("query.Parse('%v')\n%v", test.in, diff)
}
str := test.expect.String()
if diff := cmp.Diff(test.in, str); diff != "" {
t.Errorf("query.String('%v')\n%v", test.in, diff)
}
}
}
func TestCompare(t *testing.T) {
type Test struct {
a, b query.Query
expect int
}
for _, test := range []Test{
{Q("a"), Q("a"), 0},
{Q("a:*"), Q("a"), 1},
{Q("a:*"), Q("a:*"), 0},
{Q("a:*"), Q("b:*"), -1},
{Q("a:*"), Q("a:b,*"), -1},
{Q("a:b,*"), Q("a:b"), 1},
{Q("a:b,*"), Q("a:b,*"), 0},
{Q("a:b,*"), Q("a:c,*"), -1},
{Q("a:b,c,*"), Q("a:b,*"), 1},
{Q("a:b,c,*"), Q("a:b,c,*"), 0},
{Q("a:b,c,d,*"), Q("a:b,c,*"), 1},
{Q("a:b,c,*"), Q("a:b,c:d,*"), 1},
{Q("a:b,c:*"), Q("a:b,c,d,*"), -1},
{Q("a:b,c:d,*"), Q("a:b,c:d,*"), 0},
{Q("a:b,c:d,e,*"), Q("a:b,c:d,*"), 1},
{Q("a:b,c:d,e,*"), Q("a:b,c:d,e,*"), 0},
{Q("a:b,c:d,e,*"), Q("a:b,c:e,f,*"), -1},
{Q("a:b:c:d;*"), Q("a:b:c:d;*"), 0},
{Q("a:b:c:d;e=1;*"), Q("a:b:c:d;*"), 1},
{Q("a:b:c:d;e=2;*"), Q("a:b:c:d;e=1;*"), 1},
{Q("a:b:c:d;e=1;f=2;*"), Q("a:b:c:d;*"), 1},
} {
if got, expect := test.a.Compare(test.b), test.expect; got != expect {
t.Errorf("('%v').Compare('%v')\nexpect: %+v\ngot: %+v", test.a, test.b, expect, got)
}
// Check opposite order
if got, expect := test.b.Compare(test.a), -test.expect; got != expect {
t.Errorf("('%v').Compare('%v')\nexpect: %+v\ngot: %+v", test.b, test.a, expect, got)
}
}
}
func TestContains(t *testing.T) {
type Test struct {
a, b query.Query
expect bool
}
for _, test := range []Test{
{Q("a"), Q("a"), true},
{Q("a"), Q("b"), false},
{Q("a:*"), Q("a:*"), true},
{Q("a:*"), Q("a:b"), true},
{Q("a:*"), Q("b"), false},
{Q("a:*"), Q("b:c"), false},
{Q("a:*"), Q("b:*"), false},
{Q("a:*"), Q("a:b,*"), true},
{Q("a:b,*"), Q("a:*"), false},
{Q("a:b,*"), Q("a:b"), true},
{Q("a:b,*"), Q("a:c"), false},
{Q("a:b,*"), Q("a:b,*"), true},
{Q("a:b,*"), Q("a:c,*"), false},
{Q("a:b,c"), Q("a:b,c,d"), false},
{Q("a:b,c"), Q("a:b,c:d"), false},
{Q("a:b,c,*"), Q("a:b,*"), false},
{Q("a:b,c,*"), Q("a:b,c"), true},
{Q("a:b,c,*"), Q("a:b,d"), false},
{Q("a:b,c,*"), Q("a:b,c,*"), true},
{Q("a:b,c,*"), Q("a:b,c,d,*"), true},
{Q("a:b,c,*"), Q("a:b,c:d,*"), true},
{Q("a:b,c:*"), Q("a:b,c,d,*"), false},
{Q("a:b,c:d"), Q("a:b,c:d,e"), false},
{Q("a:b,c:d,*"), Q("a:b,c:d"), true},
{Q("a:b,c:d,*"), Q("a:b,c:e"), false},
{Q("a:b,c:d,*"), Q("a:b,c:d,*"), true},
{Q("a:b,c:d,*"), Q("a:b,c:d,e,*"), true},
{Q("a:b,c:d,e,*"), Q("a:b,c:d,e"), true},
{Q("a:b,c:d,e,*"), Q("a:b,c:e,e"), false},
{Q("a:b,c:d,e,*"), Q("a:b,c:d,f"), false},
{Q("a:b,c:d,e,*"), Q("a:b,c:d,e,*"), true},
{Q("a:b,c:d,e,*"), Q("a:b,c:e,f,*"), false},
{Q("a:b,c:d,e,*"), Q("a:b,c:d,*"), false},
{Q("a:b:c:d;*"), Q("a:b:c:d;*"), true},
{Q("a:b:c:d;*"), Q("a:b:c:d,e;*"), true},
{Q("a:b:c:d;*"), Q("a:b:c:d;e=1;*"), true},
{Q("a:b:c:d;*"), Q("a:b:c:d;e=1;*"), true},
{Q("a:b:c:d;*"), Q("a:b:c:d;e=1;f=2;*"), true},
{Q("a:b:c:d;e=1;*"), Q("a:b:c:d;*"), true},
{Q("a:b:c:d;e=1;f=2;*"), Q("a:b:c:d;*"), true},
{Q("a:b:c:d;e=1;*"), Q("a:b:c:d;e=2;*"), false},
{Q("a:b:c:d;e=2;*"), Q("a:b:c:d;e=1;*"), false},
{Q("a:b:c:d;e;*"), Q("a:b:c:d;e=1;*"), false},
} {
if got := test.a.Contains(test.b); got != test.expect {
t.Errorf("('%v').Contains('%v')\nexpect: %+v\ngot: %+v", test.a, test.b, test.expect, got)
}
}
}
func TestWalk(t *testing.T) {
type Segment struct {
Query query.Query
Target query.Target
Name string
}
type Test struct {
query query.Query
expect []Segment
}
for _, test := range []Test{
{
Q("suite"), []Segment{
{Q("suite"), query.Suite, "suite"},
}},
{
Q("suite:*"), []Segment{
{Q("suite"), query.Suite, "suite"},
{Q("suite:*"), query.Files, "*"},
}},
{
Q("suite:a"), []Segment{
{Q("suite"), query.Suite, "suite"},
{Q("suite:a"), query.Files, "a"},
}},
{
Q("suite:a,*"), []Segment{
{Q("suite"), query.Suite, "suite"},
{Q("suite:a"), query.Files, "a"},
{Q("suite:a,*"), query.Files, "*"},
}},
{
Q("suite:a,b"), []Segment{
{Q("suite"), query.Suite, "suite"},
{Q("suite:a"), query.Files, "a"},
{Q("suite:a,b"), query.Files, "b"},
}},
{
Q("suite:a,b:*"), []Segment{
{Q("suite"), query.Suite, "suite"},
{Q("suite:a"), query.Files, "a"},
{Q("suite:a,b"), query.Files, "b"},
{Q("suite:a,b:*"), query.Tests, "*"},
}},
{
Q("suite:a,b:c"), []Segment{
{Q("suite"), query.Suite, "suite"},
{Q("suite:a"), query.Files, "a"},
{Q("suite:a,b"), query.Files, "b"},
{Q("suite:a,b:c"), query.Tests, "c"},
}},
{
Q("suite:a,b:c,*"), []Segment{
{Q("suite"), query.Suite, "suite"},
{Q("suite:a"), query.Files, "a"},
{Q("suite:a,b"), query.Files, "b"},
{Q("suite:a,b:c"), query.Tests, "c"},
{Q("suite:a,b:c,*"), query.Tests, "*"},
}},
{
Q("suite:a,b:c,d"), []Segment{
{Q("suite"), query.Suite, "suite"},
{Q("suite:a"), query.Files, "a"},
{Q("suite:a,b"), query.Files, "b"},
{Q("suite:a,b:c"), query.Tests, "c"},
{Q("suite:a,b:c,d"), query.Tests, "d"},
}},
{
Q("suite:a,b:c,d:*"), []Segment{
{Q("suite"), query.Suite, "suite"},
{Q("suite:a"), query.Files, "a"},
{Q("suite:a,b"), query.Files, "b"},
{Q("suite:a,b:c"), query.Tests, "c"},
{Q("suite:a,b:c,d"), query.Tests, "d"},
{Q("suite:a,b:c,d:*"), query.Cases, "*"},
}},
{
Q("suite:a,b:c,d:e"), []Segment{
{Q("suite"), query.Suite, "suite"},
{Q("suite:a"), query.Files, "a"},
{Q("suite:a,b"), query.Files, "b"},
{Q("suite:a,b:c"), query.Tests, "c"},
{Q("suite:a,b:c,d"), query.Tests, "d"},
{Q("suite:a,b:c,d:e"), query.Cases, "e"},
}},
{
Q("suite:a,b:c,d:e;*"), []Segment{
{Q("suite"), query.Suite, "suite"},
{Q("suite:a"), query.Files, "a"},
{Q("suite:a,b"), query.Files, "b"},
{Q("suite:a,b:c"), query.Tests, "c"},
{Q("suite:a,b:c,d"), query.Tests, "d"},
{Q("suite:a,b:c,d:e;*"), query.Cases, "e;*"},
}},
{
Q("suite:a,b:c,d:e;f"), []Segment{
{Q("suite"), query.Suite, "suite"},
{Q("suite:a"), query.Files, "a"},
{Q("suite:a,b"), query.Files, "b"},
{Q("suite:a,b:c"), query.Tests, "c"},
{Q("suite:a,b:c,d"), query.Tests, "d"},
{Q("suite:a,b:c,d:e;f"), query.Cases, "e;f"},
}},
{
Q("suite:a,b:c,d:e;f;*"), []Segment{
{Q("suite"), query.Suite, "suite"},
{Q("suite:a"), query.Files, "a"},
{Q("suite:a,b"), query.Files, "b"},
{Q("suite:a,b:c"), query.Tests, "c"},
{Q("suite:a,b:c,d"), query.Tests, "d"},
{Q("suite:a,b:c,d:e;f;*"), query.Cases, "e;f;*"},
}},
} {
got := []Segment{}
err := test.query.Walk(func(q query.Query, t query.Target, n string) error {
got = append(got, Segment{q, t, n})
return nil
})
if err != nil {
t.Errorf("'%v'.Walk() returned %v", test.query, err)
continue
}
if diff := cmp.Diff(got, test.expect); diff != "" {
t.Errorf("'%v'.Walk()\n%v", test.query, diff)
}
}
}
type TestError struct{}
func (TestError) Error() string { return "test error" }
func TestWalkErrors(t *testing.T) {
for _, fq := range []query.Query{
Q("suite"),
Q("suite:*"),
Q("suite:a"),
Q("suite:a,*"),
Q("suite:a,b"),
Q("suite:a,b:*"),
Q("suite:a,b:c"),
Q("suite:a,b:c,*"),
Q("suite:a,b:c,d"),
Q("suite:a,b:c,d:*"),
Q("suite:a,b:c,d:e"),
Q("suite:a,b:c,d:e;*"),
Q("suite:a,b:c,d:e;f"),
Q("suite:a,b:c,d:e;f;*"),
} {
expect := TestError{}
got := fq.Walk(func(q query.Query, t query.Target, n string) error {
if q == fq {
return expect
}
return nil
})
if diff := cmp.Diff(got, expect); diff != "" {
t.Errorf("'%v'.Walk()\n%v", fq, diff)
}
}
}