tools: Add src/cts/result
Holds types that describe CTS test results 100% test coverage. Bug: dawn:1342 Change-Id: I453e87549eb992b2dcb41da4e0b6e3907ab2ed06 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/85804 Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
parent
4127abfd41
commit
9173392671
|
@ -0,0 +1,211 @@
|
||||||
|
// 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 result holds types that describe CTS test results.
|
||||||
|
package result
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"dawn.googlesource.com/dawn/tools/src/container"
|
||||||
|
"dawn.googlesource.com/dawn/tools/src/cts/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Result holds the result of a CTS test
|
||||||
|
type Result struct {
|
||||||
|
Query query.Query
|
||||||
|
Tags Tags
|
||||||
|
Status Status
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format writes the Result to the fmt.State
|
||||||
|
// The Result is printed as a single line, in the form:
|
||||||
|
// <query> <tags> <status>
|
||||||
|
// This matches the order in which results are sorted.
|
||||||
|
func (r Result) Format(f fmt.State, verb rune) {
|
||||||
|
if len(r.Tags) > 0 {
|
||||||
|
fmt.Fprintf(f, "%v %v %v", r.Query, TagsToString(r.Tags), r.Status)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(f, "%v %v", r.Query, r.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the result as a string
|
||||||
|
func (r Result) String() string {
|
||||||
|
sb := strings.Builder{}
|
||||||
|
fmt.Fprint(&sb, r)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the result from a string of the form:
|
||||||
|
// <query> <tags> <status>
|
||||||
|
// <tags> may be omitted if there were no tags.
|
||||||
|
func Parse(in string) (Result, error) {
|
||||||
|
line := in
|
||||||
|
token := func() string {
|
||||||
|
for i, c := range line {
|
||||||
|
if c != ' ' {
|
||||||
|
line = line[i:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, c := range line {
|
||||||
|
if c == ' ' {
|
||||||
|
tok := line[:i]
|
||||||
|
line = line[i:]
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tok := line
|
||||||
|
line = ""
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
|
||||||
|
a := token()
|
||||||
|
b := token()
|
||||||
|
c := token()
|
||||||
|
if a == "" || b == "" || token() != "" {
|
||||||
|
return Result{}, fmt.Errorf("unable to parse result '%v'", in)
|
||||||
|
}
|
||||||
|
q := query.Parse(a)
|
||||||
|
if c == "" {
|
||||||
|
status := Status(b)
|
||||||
|
return Result{q, nil, status}, nil
|
||||||
|
}
|
||||||
|
tags := StringToTags(b)
|
||||||
|
status := Status(c)
|
||||||
|
return Result{q, tags, status}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List is a list of results
|
||||||
|
type List []Result
|
||||||
|
|
||||||
|
// Returns the list of unique tags across all results.
|
||||||
|
func (l List) UniqueTags() []Tags {
|
||||||
|
tags := container.NewMap[string, Tags]()
|
||||||
|
for _, r := range l {
|
||||||
|
tags.Add(TagsToString(r.Tags), r.Tags)
|
||||||
|
}
|
||||||
|
return tags.Values()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransformTags returns the list of results with the tags transformed using f.
|
||||||
|
// TransformTags assumes that f will return the same output for the same input.
|
||||||
|
func (l List) TransformTags(f func(Tags) Tags) List {
|
||||||
|
cache := map[string]Tags{}
|
||||||
|
out := List{}
|
||||||
|
for _, r := range l {
|
||||||
|
key := TagsToString(r.Tags)
|
||||||
|
tags, cached := cache[key]
|
||||||
|
if !cached {
|
||||||
|
tags = f(r.Tags.Clone())
|
||||||
|
cache[key] = tags
|
||||||
|
}
|
||||||
|
out = append(out, Result{
|
||||||
|
Query: r.Query,
|
||||||
|
Tags: tags,
|
||||||
|
Status: r.Status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceDuplicates returns a new list with duplicate test results replaced.
|
||||||
|
// When a duplicate is found, the function f is called with the duplicate
|
||||||
|
// results. The returned status will be used as the replaced result.
|
||||||
|
func (l List) ReplaceDuplicates(f func(List) Status) List {
|
||||||
|
type key struct {
|
||||||
|
query query.Query
|
||||||
|
tags string
|
||||||
|
}
|
||||||
|
m := map[key]List{}
|
||||||
|
for _, r := range l {
|
||||||
|
k := key{r.Query, TagsToString(r.Tags)}
|
||||||
|
m[k] = append(m[k], r)
|
||||||
|
}
|
||||||
|
for key, results := range m {
|
||||||
|
if len(results) > 1 {
|
||||||
|
result := results[0]
|
||||||
|
result.Status = f(results)
|
||||||
|
m[key] = List{result}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out := make(List, 0, len(m))
|
||||||
|
for _, r := range l {
|
||||||
|
k := key{r.Query, TagsToString(r.Tags)}
|
||||||
|
if unique, ok := m[k]; ok {
|
||||||
|
out = append(out, unique[0])
|
||||||
|
delete(m, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort sorts the list
|
||||||
|
func (l List) Sort() {
|
||||||
|
sort.Slice(l, func(i, j int) bool {
|
||||||
|
a, b := l[i], l[j]
|
||||||
|
switch a.Query.Compare(b.Query) {
|
||||||
|
case -1:
|
||||||
|
return true
|
||||||
|
case 1:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ta := strings.Join(a.Tags.List(), TagDelimiter)
|
||||||
|
tb := strings.Join(b.Tags.List(), TagDelimiter)
|
||||||
|
switch {
|
||||||
|
case ta < tb:
|
||||||
|
return true
|
||||||
|
case ta > tb:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return a.Status < b.Status
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter returns the results that match the given predicate
|
||||||
|
func (l List) Filter(f func(Result) bool) List {
|
||||||
|
out := make(List, 0, len(l))
|
||||||
|
for _, r := range l {
|
||||||
|
if f(r) {
|
||||||
|
out = append(out, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterByStatus returns the results that the given status
|
||||||
|
func (l List) FilterByStatus(status Status) List {
|
||||||
|
return l.Filter(func(r Result) bool {
|
||||||
|
return r.Status == status
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterByTags returns the results that have all the given tags
|
||||||
|
func (l List) FilterByTags(tags Tags) List {
|
||||||
|
return l.Filter(func(r Result) bool {
|
||||||
|
return r.Tags.ContainsAll(tags)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Statuses returns a set of all the statuses in the list
|
||||||
|
func (l List) Statuses() container.Set[Status] {
|
||||||
|
set := container.NewSet[Status]()
|
||||||
|
for _, r := range l {
|
||||||
|
set.Add(r.Status)
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
}
|
|
@ -0,0 +1,849 @@
|
||||||
|
// 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 result_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"dawn.googlesource.com/dawn/tools/src/container"
|
||||||
|
"dawn.googlesource.com/dawn/tools/src/cts/query"
|
||||||
|
"dawn.googlesource.com/dawn/tools/src/cts/result"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Q = query.Parse
|
||||||
|
|
||||||
|
func T(tags ...string) result.Tags {
|
||||||
|
return result.NewTags(tags...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringAndParse(t *testing.T) {
|
||||||
|
type Test struct {
|
||||||
|
result result.Result
|
||||||
|
expect string
|
||||||
|
}
|
||||||
|
for _, test := range []Test{
|
||||||
|
{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Failure,
|
||||||
|
},
|
||||||
|
`a Failure`,
|
||||||
|
}, {
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a:b,c,*`),
|
||||||
|
Tags: T("x"),
|
||||||
|
Status: result.Pass,
|
||||||
|
},
|
||||||
|
`a:b,c,* x Pass`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a:b,c:d,*`),
|
||||||
|
Tags: T("zzz", "x", "yy"),
|
||||||
|
Status: result.Failure,
|
||||||
|
},
|
||||||
|
`a:b,c:d,* x,yy,zzz Failure`,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
if diff := cmp.Diff(test.result.String(), test.expect); diff != "" {
|
||||||
|
t.Errorf("'%v'.String() was not as expected:\n%v", test.result, diff)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parsed, err := result.Parse(test.expect)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Parse('%v') returned %v", test.expect, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(parsed, test.result); diff != "" {
|
||||||
|
t.Errorf("Parse('%v') was not as expected:\n%v", test.expect, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseError(t *testing.T) {
|
||||||
|
for _, test := range []string{
|
||||||
|
``,
|
||||||
|
`a`,
|
||||||
|
`a b c d`,
|
||||||
|
} {
|
||||||
|
_, err := result.Parse(test)
|
||||||
|
expect := fmt.Sprintf(`unable to parse result '%v'`, test)
|
||||||
|
if err == nil || err.Error() != expect {
|
||||||
|
t.Errorf("Parse('%v') returned '%v'", test, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUniqueTags(t *testing.T) {
|
||||||
|
type Test struct {
|
||||||
|
results result.List
|
||||||
|
expect []result.Tags
|
||||||
|
}
|
||||||
|
for _, test := range []Test{
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: []result.Tags{
|
||||||
|
result.NewTags(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: []result.Tags{
|
||||||
|
result.NewTags("x"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x", "y"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: []result.Tags{
|
||||||
|
result.NewTags("x", "y"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x", "y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x", "y"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: []result.Tags{
|
||||||
|
result.NewTags("x", "y"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x", "y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("y", "z"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: []result.Tags{
|
||||||
|
result.NewTags("x", "y"),
|
||||||
|
result.NewTags("y", "z"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x", "y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("y", "z"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`c`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("z", "x"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`d`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("y", "z"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: []result.Tags{
|
||||||
|
result.NewTags("x", "y"),
|
||||||
|
result.NewTags("x", "z"),
|
||||||
|
result.NewTags("y", "z"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
got := test.results.UniqueTags()
|
||||||
|
if diff := cmp.Diff(got, test.expect); diff != "" {
|
||||||
|
t.Errorf("Results:\n%v\nUniqueTags() was not as expected:\n%v", test.results, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransformTags(t *testing.T) {
|
||||||
|
type Test struct {
|
||||||
|
results result.List
|
||||||
|
transform func(result.Tags) result.Tags
|
||||||
|
expect result.List
|
||||||
|
}
|
||||||
|
for _, test := range []Test{
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transform: func(t result.Tags) result.Tags { return t },
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transform: func(got result.Tags) result.Tags {
|
||||||
|
expect := result.NewTags("x")
|
||||||
|
if diff := cmp.Diff(got, expect); diff != "" {
|
||||||
|
t.Errorf("transform function's parameter was not as expected:\n%v", diff)
|
||||||
|
}
|
||||||
|
return got
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x", "y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("y", "z"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`c`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("z", "x"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transform: func(l result.Tags) result.Tags {
|
||||||
|
l = l.Clone()
|
||||||
|
if l.Contains("x") {
|
||||||
|
l.Remove("x")
|
||||||
|
l.Add("X")
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("X", "y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("y", "z"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`c`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("z", "X"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
got := test.results.TransformTags(test.transform)
|
||||||
|
if diff := cmp.Diff(got, test.expect); diff != "" {
|
||||||
|
t.Errorf("Results:\n%v\nTransformTags() was not as expected:\n%v", test.results, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplaceDuplicates(t *testing.T) {
|
||||||
|
type Test struct {
|
||||||
|
results result.List
|
||||||
|
fn func(result.List) result.Status
|
||||||
|
expect result.List
|
||||||
|
}
|
||||||
|
for _, test := range []Test{
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
fn: func(l result.List) result.Status {
|
||||||
|
return result.Abort
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
fn: func(l result.List) result.Status {
|
||||||
|
return result.Abort
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Abort},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
fn: func(l result.List) result.Status {
|
||||||
|
return result.Abort
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Skip},
|
||||||
|
},
|
||||||
|
fn: func(got result.List) result.Status {
|
||||||
|
expect := result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Skip},
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(got, expect); diff != "" {
|
||||||
|
t.Errorf("function's parameter was not as expected:\n%v", diff)
|
||||||
|
}
|
||||||
|
return result.Abort
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Abort},
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
got := test.results.ReplaceDuplicates(test.fn)
|
||||||
|
if diff := cmp.Diff(got, test.expect); diff != "" {
|
||||||
|
t.Errorf("Results:\n%v\nReplaceDuplicates() was not as expected:\n%v", test.results, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSort(t *testing.T) {
|
||||||
|
type Test struct {
|
||||||
|
results result.List
|
||||||
|
expect result.List
|
||||||
|
}
|
||||||
|
for _, test := range []Test{
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Skip},
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Skip},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Skip,
|
||||||
|
Tags: result.NewTags(),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags(),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Skip,
|
||||||
|
Tags: result.NewTags(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("a"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("a"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("b"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("a"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("a"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`c`),
|
||||||
|
Status: result.RetryOnFailure,
|
||||||
|
Tags: result.NewTags("z"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Crash,
|
||||||
|
Tags: result.NewTags("y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Slow,
|
||||||
|
Tags: result.NewTags("y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Skip,
|
||||||
|
Tags: result.NewTags("y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Crash,
|
||||||
|
Tags: result.NewTags("x"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`aa`),
|
||||||
|
Status: result.Crash,
|
||||||
|
Tags: result.NewTags("x"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`c`),
|
||||||
|
Status: result.Abort,
|
||||||
|
Tags: result.NewTags("z"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Crash,
|
||||||
|
Tags: result.NewTags("x"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Crash,
|
||||||
|
Tags: result.NewTags("y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`aa`),
|
||||||
|
Status: result.Crash,
|
||||||
|
Tags: result.NewTags("x"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Skip,
|
||||||
|
Tags: result.NewTags("y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Slow,
|
||||||
|
Tags: result.NewTags("y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`c`),
|
||||||
|
Status: result.Abort,
|
||||||
|
Tags: result.NewTags("z"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`c`),
|
||||||
|
Status: result.RetryOnFailure,
|
||||||
|
Tags: result.NewTags("z"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
got := make(result.List, len(test.results))
|
||||||
|
copy(got, test.results)
|
||||||
|
got.Sort()
|
||||||
|
if diff := cmp.Diff(got, test.expect); diff != "" {
|
||||||
|
t.Errorf("Results:\n%v\nSort() was not as expected:\n%v", test.results, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilter(t *testing.T) {
|
||||||
|
type Test struct {
|
||||||
|
results result.List
|
||||||
|
f func(result.Result) bool
|
||||||
|
expect result.List
|
||||||
|
}
|
||||||
|
for _, test := range []Test{
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
f: func(result.Result) bool { return true },
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Failure},
|
||||||
|
},
|
||||||
|
f: func(r result.Result) bool { return r.Query == Q("b") },
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Failure},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Skip},
|
||||||
|
result.Result{Query: Q(`c`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
f: func(r result.Result) bool { return r.Status == result.Pass },
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`c`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
got := test.results.Filter(test.f)
|
||||||
|
if diff := cmp.Diff(got, test.expect); diff != "" {
|
||||||
|
t.Errorf("Results:\n%v\nFilter() was not as expected:\n%v", test.results, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterByStatus(t *testing.T) {
|
||||||
|
type Test struct {
|
||||||
|
results result.List
|
||||||
|
status result.Status
|
||||||
|
expect result.List
|
||||||
|
}
|
||||||
|
for _, test := range []Test{
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Failure},
|
||||||
|
result.Result{Query: Q(`c`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
status: result.Pass,
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`c`), Status: result.Pass},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Failure},
|
||||||
|
},
|
||||||
|
status: result.Failure,
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Failure},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{Query: Q(`a`), Status: result.Pass},
|
||||||
|
result.Result{Query: Q(`b`), Status: result.Failure},
|
||||||
|
},
|
||||||
|
status: result.RetryOnFailure,
|
||||||
|
expect: result.List{},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
got := test.results.FilterByStatus(test.status)
|
||||||
|
if diff := cmp.Diff(got, test.expect); diff != "" {
|
||||||
|
t.Errorf("Results:\n%v\nFilterByStatus(%v) was not as expected:\n%v", test.results, test.status, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterByTags(t *testing.T) {
|
||||||
|
type Test struct {
|
||||||
|
results result.List
|
||||||
|
tags result.Tags
|
||||||
|
expect result.List
|
||||||
|
}
|
||||||
|
for _, test := range []Test{
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Failure,
|
||||||
|
Tags: result.NewTags("y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`c`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x", "y"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: result.NewTags("x", "y"),
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`c`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x", "y"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Failure,
|
||||||
|
Tags: result.NewTags("y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`c`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x", "y"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: result.NewTags("x"),
|
||||||
|
expect: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`c`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x", "y"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Failure,
|
||||||
|
Tags: result.NewTags("y"),
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`c`),
|
||||||
|
Status: result.Pass,
|
||||||
|
Tags: result.NewTags("x", "y"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: result.NewTags("q"),
|
||||||
|
expect: result.List{},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
got := test.results.FilterByTags(test.tags)
|
||||||
|
if diff := cmp.Diff(got, test.expect); diff != "" {
|
||||||
|
t.Errorf("Results:\n%v\nFilterByTags(%v) was not as expected:\n%v", test.results, test.tags, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatuses(t *testing.T) {
|
||||||
|
type Test struct {
|
||||||
|
results result.List
|
||||||
|
expect container.Set[result.Status]
|
||||||
|
}
|
||||||
|
for _, test := range []Test{
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{},
|
||||||
|
expect: container.NewSet[result.Status](),
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: container.NewSet(result.Pass),
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Pass,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: container.NewSet(result.Pass),
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Skip,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: container.NewSet(result.Pass, result.Skip),
|
||||||
|
},
|
||||||
|
{ //////////////////////////////////////////////////////////////////////
|
||||||
|
results: result.List{
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`a`),
|
||||||
|
Status: result.Pass,
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`b`),
|
||||||
|
Status: result.Skip,
|
||||||
|
},
|
||||||
|
result.Result{
|
||||||
|
Query: Q(`c`),
|
||||||
|
Status: result.Failure,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: container.NewSet(result.Pass, result.Skip, result.Failure),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
got := test.results.Statuses()
|
||||||
|
if diff := cmp.Diff(got, test.expect); diff != "" {
|
||||||
|
t.Errorf("Results:\n%v\nStatuses() was not as expected:\n%v", test.results, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
// 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 result
|
||||||
|
|
||||||
|
// Status is an enumerator of test results
|
||||||
|
type Status string
|
||||||
|
|
||||||
|
// Enumerator values for Status
|
||||||
|
const (
|
||||||
|
Abort = Status("Abort")
|
||||||
|
Crash = Status("Crash")
|
||||||
|
Failure = Status("Failure")
|
||||||
|
Pass = Status("Pass")
|
||||||
|
RetryOnFailure = Status("RetryOnFailure")
|
||||||
|
Skip = Status("Skip")
|
||||||
|
Slow = Status("Slow")
|
||||||
|
Unknown = Status("Unknown")
|
||||||
|
)
|
|
@ -0,0 +1,43 @@
|
||||||
|
// 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 result
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"dawn.googlesource.com/dawn/tools/src/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tags is a collection of strings used to annotate test results with the test
|
||||||
|
// configuration.
|
||||||
|
type Tags = container.Set[string]
|
||||||
|
|
||||||
|
// Returns a new tag set with the given tags
|
||||||
|
func NewTags(tags ...string) Tags {
|
||||||
|
return Tags(container.NewSet(tags...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagsToString returns the tags sorted and joined using the TagDelimiter
|
||||||
|
func TagsToString(t Tags) string {
|
||||||
|
return strings.Join(t.List(), TagDelimiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToTags returns the tags sorted and joined using the TagDelimiter
|
||||||
|
func StringToTags(s string) Tags {
|
||||||
|
return NewTags(strings.Split(s, TagDelimiter)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The delimiter used to separate tags when stored as a string
|
||||||
|
const TagDelimiter = ","
|
Loading…
Reference in New Issue