270 lines
7.0 KiB
Go
270 lines
7.0 KiB
Go
// 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 expectations provides types and helpers for parsing, updating and
|
|
// writing WebGPU expectations files.
|
|
//
|
|
// See <dawn>/webgpu-cts/expectations.txt for more information.
|
|
package expectations
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
|
|
"dawn.googlesource.com/dawn/tools/src/cts/result"
|
|
)
|
|
|
|
// Content holds the full content of an expectations file.
|
|
type Content struct {
|
|
Chunks []Chunk
|
|
Tags Tags
|
|
}
|
|
|
|
// Chunk is an optional comment followed by a run of expectations.
|
|
// A chunk ends at the first blank line, or at the transition from an
|
|
// expectation to a line-comment.
|
|
type Chunk struct {
|
|
Comments []string // Line comments at the top of the chunk
|
|
Expectations Expectations // Expectations for the chunk
|
|
}
|
|
|
|
// Tags holds the tag information parsed in the comments between the
|
|
// 'BEGIN TAG HEADER' and 'END TAG HEADER' markers.
|
|
// Tags are grouped in tag-sets.
|
|
type Tags struct {
|
|
// Map of tag-set name to tags
|
|
Sets []TagSet
|
|
// Map of tag name to tag-set and priority
|
|
ByName map[string]TagSetAndPriority
|
|
}
|
|
|
|
// TagSet is a named collection of tags, parsed from the 'TAG HEADER'
|
|
type TagSet struct {
|
|
Name string // Name of the tag-set
|
|
Tags result.Tags // Tags belonging to the tag-set
|
|
}
|
|
|
|
// TagSetAndPriority is used by the Tags.ByName map to identify which tag-set
|
|
// a tag belongs to.
|
|
type TagSetAndPriority struct {
|
|
// The tag-set that the tag belongs to.
|
|
Set string
|
|
// The declared order of tag in the set.
|
|
// An expectation may only list a single tag from any set. This priority
|
|
// is used to decide which tag(s) should be dropped when multiple tags are
|
|
// found in the same set.
|
|
Priority int
|
|
}
|
|
|
|
// Expectation holds a single expectation line
|
|
type Expectation struct {
|
|
Line int // The 1-based line number of the expectation
|
|
Bug string // The associated bug URL for this expectation
|
|
Tags result.Tags // Tags used to filter the expectation
|
|
Query string // The CTS query
|
|
Status []string // The expected result status
|
|
Comment string // Optional comment at end of line
|
|
}
|
|
|
|
// Expectations are a list of Expectation
|
|
type Expectations []Expectation
|
|
|
|
// Load loads the expectation file at 'path', returning a Content.
|
|
func Load(path string) (Content, error) {
|
|
content, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return Content{}, err
|
|
}
|
|
ex, err := Parse(string(content))
|
|
if err != nil {
|
|
return Content{}, err
|
|
}
|
|
return ex, nil
|
|
}
|
|
|
|
// Save saves the Content file to 'path'.
|
|
func (c Content) Save(path string) error {
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
return c.Write(f)
|
|
}
|
|
|
|
// Clone makes a deep-copy of the Content.
|
|
func (c Content) Clone() Content {
|
|
chunks := make([]Chunk, len(c.Chunks))
|
|
for i, c := range c.Chunks {
|
|
chunks[i] = c.Clone()
|
|
}
|
|
return Content{chunks, c.Tags.Clone()}
|
|
}
|
|
|
|
// Empty returns true if the Content has no chunks.
|
|
func (c Content) Empty() bool {
|
|
return len(c.Chunks) == 0
|
|
}
|
|
|
|
// EndsInBlankLine returns true if the Content ends with a blank line
|
|
func (c Content) EndsInBlankLine() bool {
|
|
return !c.Empty() && c.Chunks[len(c.Chunks)-1].IsBlankLine()
|
|
}
|
|
|
|
// MaybeAddBlankLine appends a new blank line to the content, if the content
|
|
// does not already end in a blank line.
|
|
func (c *Content) MaybeAddBlankLine() {
|
|
if !c.Empty() && !c.EndsInBlankLine() {
|
|
c.Chunks = append(c.Chunks, Chunk{})
|
|
}
|
|
}
|
|
|
|
// Write writes the Content, in textual form, to the writer w.
|
|
func (c Content) Write(w io.Writer) error {
|
|
for _, chunk := range c.Chunks {
|
|
if len(chunk.Comments) == 0 && len(chunk.Expectations) == 0 {
|
|
if _, err := fmt.Fprintln(w); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
for _, comment := range chunk.Comments {
|
|
if _, err := fmt.Fprintln(w, comment); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, expectation := range chunk.Expectations {
|
|
parts := []string{}
|
|
if expectation.Bug != "" {
|
|
parts = append(parts, expectation.Bug)
|
|
}
|
|
if len(expectation.Tags) > 0 {
|
|
parts = append(parts, fmt.Sprintf("[ %v ]", strings.Join(expectation.Tags.List(), " ")))
|
|
}
|
|
parts = append(parts, expectation.Query)
|
|
parts = append(parts, fmt.Sprintf("[ %v ]", strings.Join(expectation.Status, " ")))
|
|
if expectation.Comment != "" {
|
|
parts = append(parts, expectation.Comment)
|
|
}
|
|
if _, err := fmt.Fprintln(w, strings.Join(parts, " ")); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// String returns the Content as a string.
|
|
func (c Content) String() string {
|
|
sb := strings.Builder{}
|
|
c.Write(&sb)
|
|
return sb.String()
|
|
}
|
|
|
|
// IsCommentOnly returns true if the Chunk contains comments and no expectations.
|
|
func (c Chunk) IsCommentOnly() bool {
|
|
return len(c.Comments) > 0 && len(c.Expectations) == 0
|
|
}
|
|
|
|
// IsBlankLine returns true if the Chunk has no comments or expectations.
|
|
func (c Chunk) IsBlankLine() bool {
|
|
return len(c.Comments) == 0 && len(c.Expectations) == 0
|
|
}
|
|
|
|
// Clone returns a deep-copy of the Chunk
|
|
func (c Chunk) Clone() Chunk {
|
|
comments := make([]string, len(c.Comments))
|
|
for i, c := range c.Comments {
|
|
comments[i] = c
|
|
}
|
|
expectations := make([]Expectation, len(c.Expectations))
|
|
for i, e := range c.Expectations {
|
|
expectations[i] = e.Clone()
|
|
}
|
|
return Chunk{comments, expectations}
|
|
}
|
|
|
|
// Clone returns a deep-copy of the Tags
|
|
func (t Tags) Clone() Tags {
|
|
out := Tags{}
|
|
if t.ByName != nil {
|
|
out.ByName = make(map[string]TagSetAndPriority, len(t.ByName))
|
|
for n, t := range t.ByName {
|
|
out.ByName[n] = t
|
|
}
|
|
}
|
|
if t.Sets != nil {
|
|
out.Sets = make([]TagSet, len(t.Sets))
|
|
copy(out.Sets, t.Sets)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Clone makes a deep-copy of the Expectation.
|
|
func (e Expectation) Clone() Expectation {
|
|
out := Expectation{
|
|
Line: e.Line,
|
|
Bug: e.Bug,
|
|
Query: e.Query,
|
|
Comment: e.Comment,
|
|
}
|
|
if e.Tags != nil {
|
|
out.Tags = e.Tags.Clone()
|
|
}
|
|
if e.Status != nil {
|
|
out.Status = append([]string{}, e.Status...)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Compare compares the relative order of a and b, returning:
|
|
//
|
|
// -1 if a should come before b
|
|
// 1 if a should come after b
|
|
// 0 if a and b are identical
|
|
//
|
|
// Note: Only comparing bug, query, and tags (in that order).
|
|
func (a Expectation) Compare(b Expectation) int {
|
|
switch strings.Compare(a.Bug, b.Bug) {
|
|
case -1:
|
|
return -1
|
|
case 1:
|
|
return 1
|
|
}
|
|
switch strings.Compare(a.Query, b.Query) {
|
|
case -1:
|
|
return -1
|
|
case 1:
|
|
return 1
|
|
}
|
|
aTag := result.TagsToString(a.Tags)
|
|
bTag := result.TagsToString(b.Tags)
|
|
switch strings.Compare(aTag, bTag) {
|
|
case -1:
|
|
return -1
|
|
case 1:
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (l Expectations) Sort() {
|
|
sort.Slice(l, func(i, j int) bool { return l[i].Compare(l[j]) < 0 })
|
|
}
|