| // 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/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 following assumptions: |
| // - The provided results do not have existing expectations |
| // - Any expectations for invalid/non-existent tests have already been |
| // removed |
| // |
| // This will: |
| // - Reduce result tags down to either: |
| // --- Only the most explicit tag from each set |
| // --- Only a few very broad tags |
| // - 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, |
| use_explicit_tags bool, verbose bool) error { |
| // Make a copy of the results. This code mutates the list. |
| results = append(result.List{}, results...) |
| |
| 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 use_explicit_tags { |
| if err := c.reduceTagsToMostExplicitOnly(&results); err != nil { |
| return err |
| } |
| } else { |
| if err := c.reduceTagsToBroadGroups(&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 |
| } |
| |
| // 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 |
| } |
| |
| // reduceTagsToBroadGroups modifies the given results argument in place so that |
| // all contained results' tag sets only contain several broad but important |
| // tags. |
| func (c *Content) reduceTagsToBroadGroups(results *result.List) error { |
| |
| osTagSet, gpuTagSet, deviceTagSet, err := c.getBroadTagSets() |
| if err != nil { |
| return err |
| } |
| |
| for i, res := range *results { |
| // Do an initial culling so only the broad tags are left. |
| lowPriorityTags := c.Tags.RemoveHigherPriorityTags(res.Tags) |
| chosenTags := result.NewTags() |
| |
| // If this is a remote platform, only use the device tag since OS type + |
| // GPU vendor can be inferred. |
| chosenDeviceTag, err := getAndAssertOneOverlappingTag(deviceTagSet, lowPriorityTags) |
| if err == nil { |
| chosenTags.Add(chosenDeviceTag) |
| } else { |
| chosenOsTag, err := getAndAssertOneOverlappingTag(osTagSet, lowPriorityTags) |
| if err != nil { |
| return err |
| } |
| chosenTags.Add(chosenOsTag) |
| |
| chosenGpuTag, err := getAndAssertOneOverlappingTag(gpuTagSet, lowPriorityTags) |
| if err != nil { |
| return err |
| } |
| chosenTags.Add(chosenGpuTag) |
| } |
| |
| // Modify the result's tags in place. |
| res.Tags = chosenTags |
| (*results)[i] = res |
| } |
| |
| return nil |
| } |
| |
| // getAndAssertOneOverlappingTag asserts that there is a single tag that is |
| // shared between the given tags and returns it. |
| func getAndAssertOneOverlappingTag(validTags, allTags result.Tags) (string, error) { |
| overlappingTags := validTags.Intersection(allTags) |
| if len(overlappingTags) != 1 { |
| return "", fmt.Errorf("Tags %v did not have exactly one overlap with %v", validTags, allTags) |
| } |
| return overlappingTags.One(), nil |
| } |
| |
| // getBroadTagSets retrieves the Content's tag sets which correspond to the three |
| // primary broad tags: OS, GPU, and device type. |
| func (c *Content) getBroadTagSets() (result.Tags, result.Tags, result.Tags, error) { |
| // Determine which tag sets are the ones we care about. We do this by looking |
| // for the tag set that contains one of the well-known tags of that type, e.g. |
| // "android", "linux", "mac", or "win" for OS. |
| var osTagSet, gpuTagSet, deviceTagSet result.Tags |
| // TODO(crbug.com/344014313): Move these known tags into a JSON configuration |
| // file for easy modification once dependency injection is available to make |
| // testing possible. |
| knownOsTags := result.NewTags("android", "linux", "mac", "win") |
| knownGpuTags := result.NewTags("amd", "intel", "nvidia", "qualcomm") |
| knownDeviceTags := result.NewTags("android-pixel-4", "android-pixel-6", |
| "chromeos-board-amd64-generic", "fuchsia-board-qemu-x64") |
| for _, tagSet := range c.Tags.Sets { |
| if tagSet.Tags.ContainsAny(knownOsTags) { |
| osTagSet = tagSet.Tags |
| } else if tagSet.Tags.ContainsAny(knownGpuTags) { |
| gpuTagSet = tagSet.Tags |
| } else if tagSet.Tags.ContainsAny(knownDeviceTags) { |
| deviceTagSet = tagSet.Tags |
| } |
| } |
| |
| if osTagSet == nil || gpuTagSet == nil || deviceTagSet == nil { |
| return nil, nil, nil, fmt.Errorf("Did not find expected tag sets. Known tags may be out of date. osTagSet: %v, gpuTagSet: %v, deviceTagSet: %v", |
| osTagSet, gpuTagSet, deviceTagSet) |
| } |
| |
| return osTagSet, gpuTagSet, deviceTagSet, 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 |
| } |