From 75cc17f2d1103770da41ac4308515cfcaa4e19f1 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Fri, 1 Apr 2022 16:31:15 +0000 Subject: [PATCH] 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 Commit-Queue: Ben Clayton --- tools/src/cts/query/query.go | 362 +++++++++++++ tools/src/cts/query/query_test.go | 865 ++++++++++++++++++++++++++++++ 2 files changed, 1227 insertions(+) create mode 100644 tools/src/cts/query/query.go create mode 100644 tools/src/cts/query/query_test.go diff --git a/tools/src/cts/query/query.go b/tools/src/cts/query/query.go new file mode 100644 index 0000000000..718dda35b1 --- /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, "") + } +} + +// 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 0000000000..c42f8c7e1d --- /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), ""}, + } { + 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) + } + } +}