blob: 5a107d7fd628ae18a0bee6fced3c44bc4c1aea2d [file] [log] [blame]
// Copyright 2022 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package expectations
import (
"fmt"
"strings"
"time"
"dawn.googlesource.com/dawn/tools/src/container"
"dawn.googlesource.com/dawn/tools/src/cts/query"
"dawn.googlesource.com/dawn/tools/src/cts/result"
)
const (
// Chunk comment for the AddExpectationsForFailingResults path.
ROLLER_AUTOGENERATED_FAILURES = "# ##ROLLER_AUTOGENERATED_FAILURES##"
)
// AddExpectationsForFailingResults adds new expectations for the provided
// failing results, with the assumption that the provided results do not
// have existing expectations.
//
// This will:
// - Remove expectations for non-existent tests.
// - Reduce result tags down to only the most explicit tag from each set
// - Merge identical results together
// - Add new expectations to the one mutable chunk that is expected to
// be present in the file.
// - Sort the mutable chunk's expectations by test name, then tags.
//
// TODO(crbug.com/372730248): Return diagnostics.
func (c *Content) AddExpectationsForFailingResults(results result.List,
testlist []query.Query, verbose bool) error {
// Make a copy of the results. This code mutates the list.
results = append(result.List{}, results...)
startTime := time.Now()
// TODO(crbug.com/372730248): Do this once (instead of every patchset) and/or
// find a way to optimize this. This currently takes ~99% of the result
// processing time.
if err := c.removeExpectationsForUnknownTests(&testlist); err != nil {
return err
}
if verbose {
fmt.Printf("Removing unknown expectations took %s\n", time.Now().Sub(startTime).String())
}
startTime = time.Now()
if err := c.removeUnknownTags(&results); err != nil {
return err
}
if verbose {
fmt.Printf("Removing unknown tags took %s\n", time.Now().Sub(startTime).String())
}
startTime = time.Now()
if err := c.reduceTagsToMostExplicitOnly(&results); err != nil {
return err
}
if verbose {
fmt.Printf("Reducing tags took %s\n", time.Now().Sub(startTime).String())
}
// Merge identical results.
startTime = time.Now()
results = result.Merge(results)
if verbose {
fmt.Printf("Merging identical results took %s\n", time.Now().Sub(startTime).String())
}
startTime = time.Now()
if err := c.addExpectationsToMutableChunk(&results); err != nil {
return err
}
if verbose {
fmt.Printf("Adding expectations took %s\n", time.Now().Sub(startTime).String())
}
return nil
}
// removeExpectationsForUnknownTests modifies the Content in place so that all
// contained Expectations apply to tests in the given testlist.
func (c *Content) removeExpectationsForUnknownTests(testlist *[]query.Query) error {
// Converting into a set allows us to much more efficiently check if a
// non-wildcard expectation is for a valid test.
knownTestNames := container.NewSet[string]()
for _, testQuery := range *testlist {
knownTestNames.Add(testQuery.ExpectationFileString())
}
prunedChunkSlice := make([]Chunk, 0)
for _, chunk := range c.Chunks {
prunedChunk := chunk.Clone()
// If we don't have any expectations already, just add the chunk back
// immediately to avoid removing comments, especially the header.
if prunedChunk.IsCommentOnly() {
prunedChunkSlice = append(prunedChunkSlice, prunedChunk)
continue
}
prunedChunk.Expectations = make(Expectations, 0)
for _, expectation := range chunk.Expectations {
// We don't actually parse the query string into a Query since wildcards
// are treated differently between expectations and CTS queries.
if strings.HasSuffix(expectation.Query, "*") {
testPrefix := expectation.Query[:len(expectation.Query)-1]
for testName := range knownTestNames {
if strings.HasPrefix(testName, testPrefix) {
prunedChunk.Expectations = append(prunedChunk.Expectations, expectation)
break
}
}
} else {
if knownTestNames.Contains(expectation.Query) {
prunedChunk.Expectations = append(prunedChunk.Expectations, expectation)
}
}
}
if len(prunedChunk.Expectations) > 0 {
prunedChunkSlice = append(prunedChunkSlice, prunedChunk)
}
}
c.Chunks = prunedChunkSlice
return nil
}
// removeUnknownTags modifies |results| in place so that the Results contained
// within it only use tags that the Content is aware of.
func (c *Content) removeUnknownTags(results *result.List) error {
*results = results.TransformTags(func(t result.Tags) result.Tags {
filtered := result.NewTags()
for tag := range t {
if _, ok := c.Tags.ByName[tag]; ok {
filtered.Add(tag)
}
}
return filtered
})
return nil
}
// reduceTagsToMostExplicitOnly modifies the given results argument in place
// so that all contained results' tag sets only contain the most explicit tags
// based on the known tag sets contained within the Content.
func (c *Content) reduceTagsToMostExplicitOnly(results *result.List) error {
for i, res := range *results {
res.Tags = c.Tags.RemoveLowerPriorityTags(res.Tags)
(*results)[i] = res
}
return nil
}
// addExpectationsToMutableChunk adds expectations for the results contained
// within |results| to the one mutable chunk that should be in the Content.
// If not found, a new one will be created at the end of the Content.
func (c *Content) addExpectationsToMutableChunk(results *result.List) error {
// Find the mutable chunk.
// Chunks are considered immutable by default, unless annotated as
// ROLLER_AUTOGENERATED_FAILURES.
mutableTokens := []string{
ROLLER_AUTOGENERATED_FAILURES,
}
// Bin the chunks into those that contain any of the strings in
// mutableTokens in the comments and those that do not have these strings.
immutableChunkIndicies, mutableChunkIndices := []int{}, []int{}
for i, chunk := range c.Chunks {
immutable := true
comments:
for _, line := range chunk.Comments {
for _, token := range mutableTokens {
if strings.Contains(line, token) {
immutable = false
break comments
}
}
}
if immutable {
immutableChunkIndicies = append(immutableChunkIndicies, i)
} else {
mutableChunkIndices = append(mutableChunkIndices, i)
}
}
var chunkToModify *Chunk
if len(mutableChunkIndices) > 1 {
return fmt.Errorf("Expected 1 mutable chunk, found %d", len(mutableChunkIndices))
} else if len(mutableChunkIndices) == 0 {
newChunk := Chunk{}
newChunk.Comments = []string{
"################################################################################",
"# Autogenerated Failure expectations. Please triage.",
ROLLER_AUTOGENERATED_FAILURES,
"################################################################################",
}
c.Chunks = append(c.Chunks, newChunk)
chunkToModify = &(c.Chunks[len(c.Chunks)-1])
} else {
chunkToModify = &(c.Chunks[mutableChunkIndices[0]])
}
// Add the new expectations to the mutable chunk.
for _, res := range *results {
expectation := Expectation{
Bug: "crbug.com/0000",
Tags: res.Tags,
Query: res.Query.ExpectationFileString(),
Status: []string{
"Failure",
},
}
chunkToModify.Expectations = append(chunkToModify.Expectations, expectation)
}
// Sort the mutable chunk's expectations.
chunkToModify.Expectations.SortPrioritizeQuery()
return nil
}