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>
diff --git a/tools/src/cts/query/query.go b/tools/src/cts/query/query.go
new file mode 100644
index 0000000..718dda3
--- /dev/null
+++ b/tools/src/cts/query/query.go
@@ -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
+}
diff --git a/tools/src/cts/query/query_test.go b/tools/src/cts/query/query_test.go
new file mode 100644
index 0000000..c42f8c7
--- /dev/null
+++ b/tools/src/cts/query/query_test.go
@@ -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)
+ }
+ }
+}