blob: 73b633148681742b430c4dbec6798097ef4645eb [file] [log] [blame] [edit]
// Copyright 2024 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 common
import (
"context"
"fmt"
"path/filepath"
"testing"
"time"
"dawn.googlesource.com/dawn/tools/src/cts/query"
"dawn.googlesource.com/dawn/tools/src/cts/result"
"dawn.googlesource.com/dawn/tools/src/fileutils"
"dawn.googlesource.com/dawn/tools/src/gerrit"
"dawn.googlesource.com/dawn/tools/src/oswrapper"
"dawn.googlesource.com/dawn/tools/src/resultsdb"
"github.com/stretchr/testify/require"
"go.chromium.org/luci/auth"
)
// TODO(crbug.com/342554800): Add test coverage for:
// GetResults (w/o file provided, requires refactoring and network access abstraction)
// GetUnsuppressedFailingResults (w/o file provided, ditto)
// LatestCTSRoll
// LatestPatchset
// MostRecentResultsForChange (requires os abstraction crbug.com/344014313)
// MostRecentUnsuppressedFailingResultsForChange (ditto)
/*******************************************************************************
* ResultSource tests
******************************************************************************/
func TestGetResults_FileAndPatchsetMutuallyExclusive(t *testing.T) {
testGetResults_FileAndPatchsetMutuallyExclusive_Impl(t, false)
}
func TestGetUnsuppressedFailingResults_FileAndPatchsetMutuallyExclusive(t *testing.T) {
testGetResults_FileAndPatchsetMutuallyExclusive_Impl(t, true)
}
func testGetResults_FileAndPatchsetMutuallyExclusive_Impl(t *testing.T, unsuppressedOnly bool) {
ctx := context.Background()
r := ResultSource{
File: "results.txt",
Patchset: gerrit.Patchset{
Change: 123,
},
}
var err error
if unsuppressedOnly {
_, err = r.GetUnsuppressedFailingResults(ctx, Config{}, auth.Options{})
} else {
_, err = r.GetResults(ctx, Config{}, auth.Options{})
}
require.Error(t, err)
require.ErrorContains(t, err, "invalid command line args")
}
func TestGetResults_FileWithWrapper(t *testing.T) {
testGetResults_FileWithWrapper_Impl(t, false)
}
func TestGetUnsuppressedFailingResults_FileWithWrapper(t *testing.T) {
testGetResults_FileWithWrapper_Impl(t, true)
}
func testGetResults_FileWithWrapper_Impl(t *testing.T, unsuppressedOnly bool) {
ctx := context.Background()
wrapper := oswrapper.CreateFSTestOSWrapper()
cfg := Config{
OsWrapper: wrapper,
}
r := ResultSource{
File: "results.txt",
}
resultsList := result.List{
{
Query: query.Parse("test_1"),
Tags: result.NewTags("tag_1"),
Status: result.Failure,
Duration: 0,
MayExonerate: false,
},
}
expectedResults := result.ResultsByExecutionMode{
"execution_mode": resultsList,
}
err := result.Save("results.txt", expectedResults, wrapper)
require.NoErrorf(t, err, "Got error writing results: %v", err)
var resultsByExecutionMode result.ResultsByExecutionMode
if unsuppressedOnly {
resultsByExecutionMode, err = r.GetUnsuppressedFailingResults(ctx, cfg, auth.Options{})
} else {
resultsByExecutionMode, err = r.GetResults(ctx, cfg, auth.Options{})
}
require.NoError(t, err)
require.Equal(t, expectedResults, resultsByExecutionMode)
}
/*******************************************************************************
* CacheResults tests
******************************************************************************/
func getCacheResultsSharedSetupData() (
context.Context, Config, oswrapper.FSTestOSWrapper, gerrit.Patchset, string) {
ctx := context.Background()
wrapper := oswrapper.CreateFSTestOSWrapper()
patchset := gerrit.Patchset{
Change: 1,
Patchset: 2,
}
cacheDir := "/cache"
cfg := Config{
Tests: []TestConfig{
{
ExecutionMode: result.ExecutionMode("execution_mode"),
Prefixes: []string{"prefix"},
},
},
OsWrapper: wrapper,
}
cfg.Tag.Remove = []string{"tag_to_remove"}
return ctx, cfg, wrapper, patchset, cacheDir
}
func TestCacheResults_CacheHit(t *testing.T) {
testCacheResults_CacheHit_Impl(t, false)
}
func TestCacheUnsuppressedFailingResults_CacheHit(t *testing.T) {
testCacheResults_CacheHit_Impl(t, true)
}
func testCacheResults_CacheHit_Impl(t *testing.T, unsuppressedOnly bool) {
client := resultsdb.MockBigQueryClient{}
var testedFunc cacheResultsFunc
var clientDataField *resultsdb.PrefixGroupedQueryResults
var partialCacheFilePath string
if unsuppressedOnly {
testedFunc = CacheUnsuppressedFailingResults
clientDataField = &client.UnsuppressedFailureReturnValues
partialCacheFilePath = filepath.Join("1", "ps-2-unsuppressed-failures.txt")
} else {
testedFunc = CacheResults
clientDataField = &client.ReturnValues
partialCacheFilePath = filepath.Join("1", "ps-2.txt")
}
ctx, cfg, wrapper, patchset, cacheDir := getCacheResultsSharedSetupData()
cfg.Querier = &client
// Return bad data to ensure that no querying occurs when the cache is hit.
*clientDataField = resultsdb.PrefixGroupedQueryResults{
"prefix": []resultsdb.QueryResult{
{
TestId: "bad_test",
Status: "FAIL",
Tags: []resultsdb.TagPair{},
Duration: 1.0,
},
},
}
cachedResults := result.ResultsByExecutionMode{
"execution_mode": result.List{
{
Query: query.Parse("_test_1"),
Tags: result.NewTags("tag_1"),
Status: result.Failure,
Duration: 0,
MayExonerate: false,
},
},
}
cachePath := filepath.Join(cacheDir, partialCacheFilePath)
err := result.Save(cachePath, cachedResults, wrapper)
require.NoErrorf(t, err, "Got error writing results: %v", err)
resultsByExecutionMode, err := testedFunc(ctx, cfg, patchset, cacheDir, BuildsByName{})
require.NoErrorf(t, err, "Got error caching results: %v", err)
require.Equal(t, cachedResults, resultsByExecutionMode)
}
func TestCacheResults_GetRawResultsError(t *testing.T) {
testCacheResults_GetRawResultsError_Impl(t, false)
}
func TestCacheUnsuppressedFailingResults_GetRawResultsError(t *testing.T) {
testCacheResults_GetRawResultsError_Impl(t, true)
}
func testCacheResults_GetRawResultsError_Impl(t *testing.T, unsuppressedOnly bool) {
client := resultsdb.MockBigQueryClient{}
var testedFunc cacheResultsFunc
var clientDataField *resultsdb.PrefixGroupedQueryResults
if unsuppressedOnly {
testedFunc = CacheUnsuppressedFailingResults
clientDataField = &client.UnsuppressedFailureReturnValues
} else {
testedFunc = CacheResults
clientDataField = &client.ReturnValues
}
ctx, cfg, _, patchset, cacheDir := getCacheResultsSharedSetupData()
cfg.Querier = &client
*clientDataField = resultsdb.PrefixGroupedQueryResults{
"prefix": []resultsdb.QueryResult{
{
TestId: "bad_test",
Status: "FAIL",
Tags: []resultsdb.TagPair{},
Duration: 1.0,
},
},
}
resultsByExecutionMode, err := testedFunc(ctx, cfg, patchset, cacheDir, BuildsByName{})
require.Nil(t, resultsByExecutionMode)
require.ErrorContains(t, err,
"Test ID bad_test did not start with prefix even though query should have filtered.")
}
func TestCacheResults_Success(t *testing.T) {
testCacheResults_Success_Impl(t, false)
}
func TestCacheUnsuppressedFailingResults_Success(t *testing.T) {
testCacheResults_Success_Impl(t, true)
}
func testCacheResults_Success_Impl(t *testing.T, unsuppressedOnly bool) {
client := resultsdb.MockBigQueryClient{}
var testedFunc cacheResultsFunc
var clientDataField *resultsdb.PrefixGroupedQueryResults
if unsuppressedOnly {
testedFunc = CacheUnsuppressedFailingResults
clientDataField = &client.UnsuppressedFailureReturnValues
} else {
testedFunc = CacheResults
clientDataField = &client.ReturnValues
}
ctx, cfg, _, patchset, cacheDir := getCacheResultsSharedSetupData()
cfg.Querier = &client
*clientDataField = resultsdb.PrefixGroupedQueryResults{
"prefix": []resultsdb.QueryResult{
{
TestId: "prefix_test_2",
Status: "CRASH",
Tags: []resultsdb.TagPair{
{
Key: "typ_tag",
Value: "tag_2",
},
},
Duration: 2,
},
{
TestId: "prefix_test_1",
Status: "FAIL",
Tags: []resultsdb.TagPair{
{
Key: "typ_tag",
Value: "tag_1",
},
// This should be removed by CleanResults().
{
Key: "typ_tag",
Value: "tag_to_remove",
},
},
Duration: 5,
},
// Should be merged into the above result by CleanResults()
{
TestId: "prefix_test_1",
Status: "PASS",
Tags: []resultsdb.TagPair{
{
Key: "typ_tag",
Value: "tag_1",
},
// This should be removed by CleanResults().
{
Key: "typ_tag",
Value: "tag_to_remove",
},
},
Duration: 1,
},
},
}
expectedResults := result.ResultsByExecutionMode{
"execution_mode": result.List{
{
Query: query.Parse("_test_1"),
Tags: result.NewTags("tag_1"),
Status: result.Failure,
Duration: 3000000000,
MayExonerate: false,
},
{
Query: query.Parse("_test_2"),
Tags: result.NewTags("tag_2"),
Status: result.Crash,
Duration: 2000000000,
MayExonerate: false,
},
},
}
// Check that the initial results are retrieved and cleaned properly.
resultsByExecutionMode, err := testedFunc(
ctx, cfg, patchset, cacheDir, BuildsByName{})
require.NoErrorf(t, err, "Got error caching results: %v", err)
require.Equal(t, expectedResults, resultsByExecutionMode)
// Check that the results were cached and that hitting the cache still results
// in cleaned data.
client = resultsdb.MockBigQueryClient{}
cfg.Querier = &client
resultsByExecutionMode, err = testedFunc(
ctx, cfg, patchset, cacheDir, BuildsByName{})
require.NoErrorf(t, err, "Got error caching results: %v", err)
require.Equal(t, expectedResults, resultsByExecutionMode)
}
/*******************************************************************************
* GetResults tests
******************************************************************************/
func generateGoodGetResultsInputs() (
context.Context, Config, *resultsdb.MockBigQueryClient, BuildsByName) {
ctx := context.Background()
cfg := Config{
Tests: []TestConfig{
TestConfig{
ExecutionMode: result.ExecutionMode("execution_mode"),
Prefixes: []string{"prefix"},
},
},
}
client := &resultsdb.MockBigQueryClient{
ReturnValues: resultsdb.PrefixGroupedQueryResults{
"prefix": []resultsdb.QueryResult{
resultsdb.QueryResult{
TestId: "prefix_test",
Status: "PASS",
Tags: []resultsdb.TagPair{},
Duration: 1.0,
},
},
},
}
cfg.Querier = client
builds := make(BuildsByName)
return ctx, cfg, client, builds
}
// Tests that valid results are properly cleaned, sorted, and returned.
func TestGetResultsHappyPath(t *testing.T) {
ctx, cfg, client, builds := generateGoodGetResultsInputs()
cfg.Tag.Remove = []string{
"remove_me",
}
client.ReturnValues = resultsdb.PrefixGroupedQueryResults{
"prefix": []resultsdb.QueryResult{
resultsdb.QueryResult{
TestId: "prefix_test_2",
Status: "PASS",
Tags: []resultsdb.TagPair{
resultsdb.TagPair{
Key: "typ_tag",
Value: "remove_me",
},
resultsdb.TagPair{
Key: "typ_tag",
Value: "win",
},
},
Duration: 2.0,
},
resultsdb.QueryResult{
TestId: "prefix_test_1",
Status: "PASS",
Tags: []resultsdb.TagPair{
resultsdb.TagPair{
Key: "typ_tag",
Value: "linux",
},
},
Duration: 1.0,
},
},
}
expectedResultsList := result.List{
result.Result{
Query: query.Parse("_test_1"),
Status: result.Pass,
Tags: result.NewTags("linux"),
Duration: time.Second,
MayExonerate: false,
},
result.Result{
Query: query.Parse("_test_2"),
Status: result.Pass,
Tags: result.NewTags("win"),
Duration: 2 * time.Second,
MayExonerate: false,
},
}
expectedResults := make(result.ResultsByExecutionMode)
expectedResults["execution_mode"] = expectedResultsList
results, err := GetResults(ctx, cfg, builds)
require.NoError(t, err)
require.Equal(t, expectedResults, results)
}
// Tests that errors from GetRawResults are properly surfaced.
func TestGetResultsGetRawResultsErrorSurfaced(t *testing.T) {
ctx, cfg, client, builds := generateGoodGetResultsInputs()
client.ReturnValues["prefix"][0].TestId = "bad_test"
results, err := GetResults(ctx, cfg, builds)
require.Nil(t, results)
require.ErrorContains(t, err, "Test ID bad_test did not start with prefix even though query should have filtered.")
}
/*******************************************************************************
* GetUnsuppressedFailingResults tests
******************************************************************************/
func generateGoodGetUnsuppressedFailingResultsInputs() (
context.Context, Config, *resultsdb.MockBigQueryClient, BuildsByName) {
ctx := context.Background()
cfg := Config{
Tests: []TestConfig{
TestConfig{
ExecutionMode: result.ExecutionMode("execution_mode"),
Prefixes: []string{"prefix"},
},
},
}
client := &resultsdb.MockBigQueryClient{
UnsuppressedFailureReturnValues: resultsdb.PrefixGroupedQueryResults{
"prefix": []resultsdb.QueryResult{
resultsdb.QueryResult{
TestId: "prefix_test",
Status: "FAIL",
Tags: []resultsdb.TagPair{},
Duration: 1.0,
},
},
},
}
cfg.Querier = client
builds := make(BuildsByName)
return ctx, cfg, client, builds
}
// Tests that valid results are properly cleaned, sorted, and returned.
func TestGetUnsuppressedFailingResultsHappyPath(t *testing.T) {
ctx, cfg, client, builds := generateGoodGetUnsuppressedFailingResultsInputs()
cfg.Tag.Remove = []string{
"remove_me",
}
client.UnsuppressedFailureReturnValues = resultsdb.PrefixGroupedQueryResults{
"prefix": []resultsdb.QueryResult{
resultsdb.QueryResult{
TestId: "prefix_test_2",
Status: "FAIL",
Tags: []resultsdb.TagPair{
resultsdb.TagPair{
Key: "typ_tag",
Value: "remove_me",
},
resultsdb.TagPair{
Key: "typ_tag",
Value: "win",
},
},
Duration: 2.0,
},
resultsdb.QueryResult{
TestId: "prefix_test_1",
Status: "FAIL",
Tags: []resultsdb.TagPair{
resultsdb.TagPair{
Key: "typ_tag",
Value: "linux",
},
},
Duration: 1.0,
},
},
}
expectedResultsList := result.List{
result.Result{
Query: query.Parse("_test_1"),
Status: result.Failure,
Tags: result.NewTags("linux"),
Duration: time.Second,
MayExonerate: false,
},
result.Result{
Query: query.Parse("_test_2"),
Status: result.Failure,
Tags: result.NewTags("win"),
Duration: 2 * time.Second,
MayExonerate: false,
},
}
expectedResults := make(result.ResultsByExecutionMode)
expectedResults["execution_mode"] = expectedResultsList
results, err := GetUnsuppressedFailingResults(ctx, cfg, builds)
require.NoError(t, err)
require.Equal(t, expectedResults, results)
}
// Tests that errors from GetRawResults are properly surfaced.
func TestGetUnsuppressedFailingResultsGetRawResultsErrorSurfaced(t *testing.T) {
ctx, cfg, client, builds := generateGoodGetUnsuppressedFailingResultsInputs()
client.UnsuppressedFailureReturnValues["prefix"][0].TestId = "bad_test"
results, err := GetUnsuppressedFailingResults(ctx, cfg, builds)
require.Nil(t, results)
require.ErrorContains(t, err, "Test ID bad_test did not start with prefix even though query should have filtered.")
}
/*******************************************************************************
* GetRawResults tests
******************************************************************************/
// Tests that valid results are properly parsed and returned.
func TestGetRawResultsHappyPath(t *testing.T) {
ctx, cfg, client, builds := generateGoodGetResultsInputs()
client.ReturnValues = resultsdb.PrefixGroupedQueryResults{
"prefix": []resultsdb.QueryResult{
resultsdb.QueryResult{
TestId: "prefix_test_1",
Status: "PASS",
Tags: []resultsdb.TagPair{},
Duration: 1.0,
},
resultsdb.QueryResult{
TestId: "prefix_test_2",
Status: "FAIL",
Tags: []resultsdb.TagPair{
resultsdb.TagPair{
Key: "javascript_duration",
Value: "0.5s",
},
},
Duration: 2.0,
},
resultsdb.QueryResult{
TestId: "prefix_test_3",
Status: "SKIP",
Tags: []resultsdb.TagPair{
resultsdb.TagPair{
Key: "may_exonerate",
Value: "true",
},
},
Duration: 3.0,
},
resultsdb.QueryResult{
TestId: "prefix_test_4",
Status: "SomeStatus",
Tags: []resultsdb.TagPair{
resultsdb.TagPair{
Key: "typ_tag",
Value: "linux",
},
resultsdb.TagPair{
Key: "typ_tag",
Value: "intel",
},
},
Duration: 4.0,
},
},
}
expectedResultsList := result.List{
result.Result{
Query: query.Parse("_test_1"),
Status: result.Pass,
Tags: result.NewTags(),
Duration: time.Second,
MayExonerate: false,
},
result.Result{
Query: query.Parse("_test_2"),
Status: result.Failure,
Tags: result.NewTags(),
Duration: 500 * time.Millisecond,
MayExonerate: false,
},
result.Result{
Query: query.Parse("_test_3"),
Status: result.Skip,
Tags: result.NewTags(),
Duration: 3 * time.Second,
MayExonerate: true,
},
result.Result{
Query: query.Parse("_test_4"),
Status: result.Unknown,
Tags: result.NewTags("linux", "intel"),
Duration: 4 * time.Second,
MayExonerate: false,
},
}
expectedResults := make(result.ResultsByExecutionMode)
expectedResults["execution_mode"] = expectedResultsList
results, err := GetRawResults(ctx, cfg, builds)
require.NoError(t, err)
require.Equal(t, expectedResults, results)
}
// Tests that a mismatched prefix results in an error.
func TestGetRawResultsPrefixMismatch(t *testing.T) {
ctx, cfg, client, builds := generateGoodGetResultsInputs()
client.ReturnValues["prefix"][0].TestId = "bad_test"
results, err := GetRawResults(ctx, cfg, builds)
require.Nil(t, results)
require.ErrorContains(t, err, "Test ID bad_test did not start with prefix even though query should have filtered.")
}
// Tests that a JavaScript duration that cannot be parsed results in an error.
func TestGetRawResultsBadJavaScriptDuration(t *testing.T) {
ctx, cfg, client, builds := generateGoodGetResultsInputs()
client.ReturnValues["prefix"][0].Tags = []resultsdb.TagPair{
resultsdb.TagPair{
Key: "javascript_duration",
Value: "1000foo",
},
}
results, err := GetRawResults(ctx, cfg, builds)
require.Nil(t, results)
require.ErrorContains(t, err, `time: unknown unit "foo" in duration "1000foo"`)
}
// Tests that a non-boolean may_exonerate value results in an error.
func TestGetRawResultsBadMayExonerate(t *testing.T) {
ctx, cfg, client, builds := generateGoodGetResultsInputs()
client.ReturnValues["prefix"][0].Tags = []resultsdb.TagPair{
resultsdb.TagPair{
Key: "may_exonerate",
Value: "yesnt",
},
}
results, err := GetRawResults(ctx, cfg, builds)
require.Nil(t, results)
require.ErrorContains(t, err, `strconv.ParseBool: parsing "yesnt": invalid syntax`)
}
// Tests that new test IDs are filtered correctly based on their gpu_test_class
// tag.
func TestGetRawResultsV2Filtering(t *testing.T) {
ctx, _, client, builds := generateGoodGetResultsInputs()
cfg := Config{
Tests: []TestConfig{
{
ExecutionMode: "core",
Prefixes: []string{
"ninja://chrome/test:telemetry_gpu_integration_test/gpu_tests.webgpu_cts_integration_test.WebGpuCtsIntegrationTest.",
"://chrome/test\\:telemetry_gpu_integration_test!webgpucts:",
},
},
{
ExecutionMode: "compat",
Prefixes: []string{
"ninja://chrome/test:telemetry_gpu_integration_test/gpu_tests.webgpu_compat_cts_integration_test.WebGpuCompatCtsIntegrationTest.",
"://chrome/test\\:telemetry_gpu_integration_test!webgpucts:",
},
},
},
}
cfg.Querier = client
v2Prefix := "://chrome/test\\:telemetry_gpu_integration_test!webgpucts:"
client.ReturnValues = resultsdb.PrefixGroupedQueryResults{
v2Prefix: []resultsdb.QueryResult{
{
TestId: v2Prefix + "test1",
Status: "PASS",
Tags: []resultsdb.TagPair{
{
Key: "gpu_test_class",
Value: "gpu_tests.webgpu_cts_integration_test.WebGpuCtsIntegrationTest",
},
},
},
{
TestId: v2Prefix + "test2",
Status: "PASS",
Tags: []resultsdb.TagPair{
{
Key: "gpu_test_class",
Value: "gpu_tests.webgpu_compat_cts_integration_test.WebGpuCompatCtsIntegrationTest",
},
},
},
{
TestId: v2Prefix + "test3",
Status: "PASS",
Tags: []resultsdb.TagPair{
{
Key: "gpu_test_class",
Value: "gpu_tests.webgpu_cts_integration_test.WebGpuCtsIntegrationTest",
},
},
},
},
}
results, err := GetRawResults(ctx, cfg, builds)
require.NoError(t, err)
coreResults := results["core"]
compatResults := results["compat"]
require.Len(t, coreResults, 2)
require.Equal(t, "test1", coreResults[0].Query.String())
require.Equal(t, "test3", coreResults[1].Query.String())
require.Len(t, compatResults, 1)
require.Equal(t, "test2", compatResults[0].Query.String())
}
// Tests that new test IDs are transformed to the legacy format correctly.
func TestGetRawResultsV2TestNameTransformation(t *testing.T) {
ctx, cfg, client, builds := generateGoodGetResultsInputs()
v2Prefix := "://chrome/test\\:telemetry_gpu_integration_test!webgpucts:"
cfg.Tests[0].Prefixes = []string{v2Prefix}
client.ReturnValues = resultsdb.PrefixGroupedQueryResults{
v2Prefix: []resultsdb.QueryResult{
{
TestId: v2Prefix + `webgpu\:api,operation,render_pass,storeop2:storeOp_controls_whether_1x1_drawn_quad_is_stored#storeOp="discard":`,
Status: "PASS",
Tags: []resultsdb.TagPair{},
},
{
TestId: v2Prefix + `webgpu:test,bar#single_case`,
Status: "PASS",
Tags: []resultsdb.TagPair{},
},
},
}
results, err := GetRawResults(ctx, cfg, builds)
require.NoError(t, err)
executionResults := results["execution_mode"]
require.Len(t, executionResults, 2)
expected := result.List{
result.Result{
Query: query.Parse(`webgpu:api,operation,render_pass,storeop2:storeOp_controls_whether_1x1_drawn_quad_is_stored:storeOp="discard"`),
Status: result.Pass,
Tags: result.NewTags(),
},
result.Result{
Query: query.Parse(`webgpu:test,bar`),
Status: result.Pass,
Tags: result.NewTags(),
},
}
// Durations are zero-initialized
expected[0].Duration = 0
expected[1].Duration = 0
expected[0].MayExonerate = false
expected[1].MayExonerate = false
require.Equal(t, expected, executionResults)
}
/*******************************************************************************
* convertRdbStatus tests
******************************************************************************/
func TestConvertRdbStatus(t *testing.T) {
tests := []struct {
name string
input string
want result.Status
}{
{
name: "Unknown",
input: "asdf",
want: result.Unknown,
},
{
name: "Pass",
input: "PASS",
want: result.Pass,
},
{
name: "Failure",
input: "FAIL",
want: result.Failure,
},
{
name: "Crash",
input: "CRASH",
want: result.Crash,
},
{
name: "Abort",
input: "ABORT",
want: result.Abort,
},
{
name: "Skip",
input: "SKIP",
want: result.Skip,
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
status := convertRdbStatus(testCase.input)
require.Equal(t, testCase.want, status)
})
}
}
/*******************************************************************************
* GetRawUnsuppressedFailingResults tests
******************************************************************************/
// Tests that valid results are properly parsed and returned.
func TestGetRawUnsuppressedFailingResultsHappyPath(t *testing.T) {
ctx, cfg, client, builds := generateGoodGetUnsuppressedFailingResultsInputs()
client.UnsuppressedFailureReturnValues = resultsdb.PrefixGroupedQueryResults{
"prefix": []resultsdb.QueryResult{
resultsdb.QueryResult{
TestId: "prefix_test_1",
Status: "FAIL",
Tags: []resultsdb.TagPair{},
Duration: 1.0,
},
resultsdb.QueryResult{
TestId: "prefix_test_2",
Status: "FAIL",
Tags: []resultsdb.TagPair{
resultsdb.TagPair{
Key: "javascript_duration",
Value: "0.5s",
},
},
Duration: 2.0,
},
resultsdb.QueryResult{
TestId: "prefix_test_3",
Status: "FAIL",
Tags: []resultsdb.TagPair{
resultsdb.TagPair{
Key: "may_exonerate",
Value: "true",
},
},
Duration: 3.0,
},
resultsdb.QueryResult{
TestId: "prefix_test_4",
Status: "SomeStatus",
Tags: []resultsdb.TagPair{
resultsdb.TagPair{
Key: "typ_tag",
Value: "linux",
},
resultsdb.TagPair{
Key: "typ_tag",
Value: "intel",
},
},
Duration: 4.0,
},
},
}
expectedResultsList := result.List{
result.Result{
Query: query.Parse("_test_1"),
Status: result.Failure,
Tags: result.NewTags(),
Duration: time.Second,
MayExonerate: false,
},
result.Result{
Query: query.Parse("_test_2"),
Status: result.Failure,
Tags: result.NewTags(),
Duration: 500 * time.Millisecond,
MayExonerate: false,
},
result.Result{
Query: query.Parse("_test_3"),
Status: result.Failure,
Tags: result.NewTags(),
Duration: 3 * time.Second,
MayExonerate: true,
},
result.Result{
Query: query.Parse("_test_4"),
Status: result.Unknown,
Tags: result.NewTags("linux", "intel"),
Duration: 4 * time.Second,
MayExonerate: false,
},
}
expectedResults := make(result.ResultsByExecutionMode)
expectedResults["execution_mode"] = expectedResultsList
results, err := GetRawUnsuppressedFailingResults(ctx, cfg, builds)
require.NoError(t, err)
require.Equal(t, expectedResults, results)
}
// Tests that a mismatched prefix results in an error.
func TestGetRawUnsuppressedFailingResultsPrefixMismatch(t *testing.T) {
ctx, cfg, client, builds := generateGoodGetUnsuppressedFailingResultsInputs()
client.UnsuppressedFailureReturnValues["prefix"][0].TestId = "bad_test"
results, err := GetRawUnsuppressedFailingResults(ctx, cfg, builds)
require.Nil(t, results)
require.ErrorContains(t, err, "Test ID bad_test did not start with prefix even though query should have filtered.")
}
// Tests that a JavaScript duration that cannot be parsed results in an error.
func TestGetRawUnsuppressedFailingResultsBadJavaScriptDuration(t *testing.T) {
ctx, cfg, client, builds := generateGoodGetUnsuppressedFailingResultsInputs()
client.UnsuppressedFailureReturnValues["prefix"][0].Tags = []resultsdb.TagPair{
resultsdb.TagPair{
Key: "javascript_duration",
Value: "1000foo",
},
}
results, err := GetRawUnsuppressedFailingResults(ctx, cfg, builds)
require.Nil(t, results)
require.ErrorContains(t, err, `time: unknown unit "foo" in duration "1000foo"`)
}
// Tests that a non-boolean may_exonerate value results in an error.
func TestGetRawUnsuppressedFailingResultsBadMayExonerate(t *testing.T) {
ctx, cfg, client, builds := generateGoodGetUnsuppressedFailingResultsInputs()
client.UnsuppressedFailureReturnValues["prefix"][0].Tags = []resultsdb.TagPair{
resultsdb.TagPair{
Key: "may_exonerate",
Value: "yesnt",
},
}
results, err := GetRawUnsuppressedFailingResults(ctx, cfg, builds)
require.Nil(t, results)
require.ErrorContains(t, err, `strconv.ParseBool: parsing "yesnt": invalid syntax`)
}
/*******************************************************************************
* CleanResults tests
******************************************************************************/
// Tests that tags specified for removal are properly removed.
func TestCleanResultsTagRemoval(t *testing.T) {
cfg := Config{}
cfg.Tag.Remove = []string{
"remove_1",
"remove_2",
"remove_3",
"missing",
}
results := result.List{
result.Result{
Query: query.Parse("test_1"),
Status: result.Pass,
Tags: result.NewTags("remove_1", "remove_2", "linux"),
Duration: time.Second,
MayExonerate: false,
},
result.Result{
Query: query.Parse("test_2"),
Status: result.Pass,
Tags: result.NewTags("remove_2", "remove_3"),
Duration: time.Second,
MayExonerate: false,
},
}
expectedResults := result.List{
result.Result{
Query: query.Parse("test_1"),
Status: result.Pass,
Tags: result.NewTags("linux"),
Duration: time.Second,
MayExonerate: false,
},
result.Result{
Query: query.Parse("test_2"),
Status: result.Pass,
Tags: result.NewTags(),
Duration: time.Second,
MayExonerate: false,
},
}
CleanResults(cfg, &results)
require.Equal(t, expectedResults, results)
}
// Tests that duplicate results with the same status always use that status.
func TestCleanResultsDuplicateResultSameStatus(t *testing.T) {
cfg := Config{}
for _, status := range []result.Status{result.Abort, result.Crash,
result.Failure, result.Pass, result.RetryOnFailure, result.Skip, result.Slow, result.Unknown} {
results := result.List{
result.Result{
Query: query.Parse("test_1"),
Status: status,
Tags: result.NewTags(),
Duration: time.Second,
MayExonerate: false,
},
result.Result{
Query: query.Parse("test_1"),
Status: status,
Tags: result.NewTags(),
Duration: 3 * time.Second,
MayExonerate: false,
},
}
expectedResults := result.List{
result.Result{
Query: query.Parse("test_1"),
Status: status,
Tags: result.NewTags(),
Duration: 2 * time.Second,
MayExonerate: false,
},
}
CleanResults(cfg, &results)
require.Equal(t, expectedResults, results)
}
}
func runPriorityTest(t *testing.T, testedStatus result.Status, lowerPriorityStatuses *[]result.Status) {
cfg := Config{}
for _, status := range *lowerPriorityStatuses {
results := result.List{
result.Result{
Query: query.Parse("test_1"),
Status: status,
Tags: result.NewTags(),
Duration: time.Second,
MayExonerate: false,
},
result.Result{
Query: query.Parse("test_1"),
Status: testedStatus,
Tags: result.NewTags(),
Duration: 3 * time.Second,
MayExonerate: false,
},
}
expectedResults := result.List{
result.Result{
Query: query.Parse("test_1"),
Status: testedStatus,
Tags: result.NewTags(),
Duration: 2 * time.Second,
MayExonerate: false,
},
}
CleanResults(cfg, &results)
require.Equal(t, expectedResults, results)
}
}
// Tests that Crash has the highest priority among statuses.
func TestCleanResultsReplaceResultCrash(t *testing.T) {
lowerPriorityStatuses := []result.Status{result.Abort, result.Failure, result.Slow, result.Pass, result.RetryOnFailure, result.Skip, result.Unknown}
runPriorityTest(t, result.Crash, &lowerPriorityStatuses)
}
// Tests that Abort has the second highest priority among statuses.
func TestCleanResultsReplaceResultAbort(t *testing.T) {
lowerPriorityStatuses := []result.Status{result.Failure, result.Slow, result.Pass, result.RetryOnFailure, result.Skip, result.Unknown}
runPriorityTest(t, result.Abort, &lowerPriorityStatuses)
}
// Tests that Failure has the third highest priority among statuses.
func TestCleanResultsReplaceResultFailure(t *testing.T) {
lowerPriorityStatuses := []result.Status{result.Slow, result.Pass, result.RetryOnFailure, result.Skip, result.Unknown}
runPriorityTest(t, result.Failure, &lowerPriorityStatuses)
}
// Tests that Slow has the fourth highest priority among statuses.
func TestCleanResultsReplaceResultSlow(t *testing.T) {
lowerPriorityStatuses := []result.Status{result.Pass, result.RetryOnFailure, result.Skip, result.Unknown}
runPriorityTest(t, result.Slow, &lowerPriorityStatuses)
}
// Tests that statuses without explicit priority default to Failure.
func TestCleanResultsReplaceResultDefault(t *testing.T) {
cfg := Config{}
statuses := []result.Status{result.Pass, result.RetryOnFailure, result.Skip, result.Unknown}
for i, leftStatus := range statuses {
for j, rightStatus := range statuses {
if i == j {
continue
}
results := result.List{
result.Result{
Query: query.Parse("test_1"),
Status: leftStatus,
Tags: result.NewTags(),
Duration: time.Second,
MayExonerate: false,
},
result.Result{
Query: query.Parse("test_1"),
Status: rightStatus,
Tags: result.NewTags(),
Duration: 3 * time.Second,
MayExonerate: false,
},
}
expectedResults := result.List{
result.Result{
Query: query.Parse("test_1"),
Status: result.Failure,
Tags: result.NewTags(),
Duration: 2 * time.Second,
MayExonerate: false,
},
}
CleanResults(cfg, &results)
require.Equal(t, expectedResults, results)
}
}
}
/*******************************************************************************
* CacheRecentUniqueSuppressed tests
******************************************************************************/
func getMultiPrefixConfig() Config {
return Config{
Tests: []TestConfig{
TestConfig{
ExecutionMode: result.ExecutionMode("core"),
Prefixes: []string{"core_prefix"},
},
TestConfig{
ExecutionMode: result.ExecutionMode("compat"),
Prefixes: []string{"compat_prefix"},
},
},
}
}
func getMultiPrefixQueryResults() resultsdb.PrefixGroupedQueryResults {
return resultsdb.PrefixGroupedQueryResults{
"core_prefix": []resultsdb.QueryResult{
{
TestId: "core_prefix_test_1",
Status: "FAIL",
Tags: []resultsdb.TagPair{
{
Key: "typ_tag",
Value: "tag_1",
},
},
Duration: 1.0,
},
{
TestId: "core_prefix_test_2",
Status: "FAIL",
Tags: []resultsdb.TagPair{
{
Key: "typ_tag",
Value: "tag_2",
},
},
Duration: 1.0,
},
},
"compat_prefix": []resultsdb.QueryResult{
{
TestId: "compat_prefix_test_3",
Status: "FAIL",
Tags: []resultsdb.TagPair{
{
Key: "typ_tag",
Value: "tag_3",
},
},
Duration: 1.0,
},
{
TestId: "compat_prefix_test_4",
Status: "FAIL",
Tags: []resultsdb.TagPair{
{
Key: "typ_tag",
Value: "tag_4",
},
},
Duration: 1.0,
},
},
}
}
func getExpectedMultiPrefixResults() result.ResultsByExecutionMode {
return result.ResultsByExecutionMode{
"core": result.List{
{
Query: query.Parse("_test_1"),
Tags: result.NewTags("tag_1"),
Status: result.Pass,
Duration: 0,
MayExonerate: false,
},
{
Query: query.Parse("_test_2"),
Tags: result.NewTags("tag_2"),
Status: result.Pass,
Duration: 0,
MayExonerate: false,
},
},
"compat": result.List{
{
Query: query.Parse("_test_3"),
Tags: result.NewTags("tag_3"),
Status: result.Pass,
Duration: 0,
MayExonerate: false,
},
{
Query: query.Parse("_test_4"),
Tags: result.NewTags("tag_4"),
Status: result.Pass,
Duration: 0,
MayExonerate: false,
},
},
}
}
func TestCacheRecentUniqueSuppressedCoreResults_ErrorSurfaced(t *testing.T) {
ctx := context.Background()
cfg := getMultiPrefixConfig()
wrapper := oswrapper.CreateFSTestOSWrapper()
results := getMultiPrefixQueryResults()
results["core_prefix"][0].TestId = "bad_test"
client := resultsdb.MockBigQueryClient{
RecentUniqueSuppressedReturnValues: results,
}
cfg.Querier = &client
resultsList, err := CacheRecentUniqueSuppressedCoreResults(
ctx, cfg, fileutils.ThisDir(), wrapper)
require.Nil(t, resultsList)
require.ErrorContains(t, err,
"Test ID bad_test did not start with core_prefix even though query should have filtered.")
}
func TestCacheRecentUniqueSuppressedCoreResults_Success(t *testing.T) {
ctx := context.Background()
cfg := getMultiPrefixConfig()
wrapper := oswrapper.CreateFSTestOSWrapper()
client := resultsdb.MockBigQueryClient{
RecentUniqueSuppressedReturnValues: getMultiPrefixQueryResults(),
}
cfg.Querier = &client
resultsList, err := CacheRecentUniqueSuppressedCoreResults(
ctx, cfg, fileutils.ThisDir(), wrapper)
require.NoErrorf(t, err, "Error getting results: %v", err)
require.Equal(t, resultsList, getExpectedMultiPrefixResults()["core"])
}
func TestCAcheRecentUniqueSuppressedCompatResults_ErrorSurfaced(t *testing.T) {
ctx := context.Background()
cfg := getMultiPrefixConfig()
wrapper := oswrapper.CreateFSTestOSWrapper()
results := getMultiPrefixQueryResults()
results["compat_prefix"][0].TestId = "bad_test"
client := resultsdb.MockBigQueryClient{
RecentUniqueSuppressedReturnValues: results,
}
cfg.Querier = &client
resultsList, err := CacheRecentUniqueSuppressedCoreResults(
ctx, cfg, fileutils.ThisDir(), wrapper)
require.Nil(t, resultsList)
require.ErrorContains(t, err,
"Test ID bad_test did not start with compat_prefix even though query should have filtered.")
}
func TestCacheRecentUniqueSuppressedCompatResults_Success(t *testing.T) {
ctx := context.Background()
cfg := getMultiPrefixConfig()
wrapper := oswrapper.CreateFSTestOSWrapper()
client := resultsdb.MockBigQueryClient{
RecentUniqueSuppressedReturnValues: getMultiPrefixQueryResults(),
}
cfg.Querier = &client
resultsList, err := CacheRecentUniqueSuppressedCompatResults(
ctx, cfg, fileutils.ThisDir(), wrapper)
require.NoErrorf(t, err, "Error getting results: %v", err)
require.Equal(t, resultsList, getExpectedMultiPrefixResults()["compat"])
}
func TestCacheRecentUniqueSuppressedResults_CacheHit(t *testing.T) {
ctx := context.Background()
cfg := getMultiPrefixConfig()
wrapper := oswrapper.CreateFSTestOSWrapper()
// Technically this could run into a race condition if we run this test at the
// exact time the day changes so the file is created on a different day than
// it's read, but that seems exceedingly unlikely in practice.
year, month, day := time.Now().Date()
result.Save(
filepath.Join(
fileutils.ThisDir(),
"expectation-affected-ci-results",
fmt.Sprintf("%d-%d-%d.txt", year, month, day)),
getExpectedMultiPrefixResults(),
wrapper)
client := resultsdb.MockBigQueryClient{}
cfg.Querier = &client
resultsByExecutionMode, err := CacheRecentUniqueSuppressedResults(
ctx, cfg, fileutils.ThisDir(), wrapper)
require.NoErrorf(t, err, "Error getting results: %v", err)
require.Equal(t, getExpectedMultiPrefixResults(), resultsByExecutionMode)
}
func TestCacheRecentUniqueSuppressedResults_CacheSkippedIfUnspecified(t *testing.T) {
ctx := context.Background()
cfg := getMultiPrefixConfig()
wrapper := oswrapper.CreateFSTestOSWrapper()
modifiedResults := getExpectedMultiPrefixResults()
modifiedResults["core"] = append(modifiedResults["core"], result.Result{
Query: query.Parse("_test_5"),
Tags: result.NewTags("tag_5"),
Status: result.Pass,
Duration: 0,
MayExonerate: false,
})
year, month, day := time.Now().Date()
result.Save(
filepath.Join(
"expectation-affected-ci-results",
fmt.Sprintf("%d-%d-%d.txt", year, month, day)),
modifiedResults,
wrapper)
client := resultsdb.MockBigQueryClient{
RecentUniqueSuppressedReturnValues: getMultiPrefixQueryResults(),
}
cfg.Querier = &client
resultsByExecutionMode, err := CacheRecentUniqueSuppressedResults(
ctx, cfg, "", wrapper)
require.NoErrorf(t, err, "Error getting results: %v", err)
require.Equal(t, getExpectedMultiPrefixResults(), resultsByExecutionMode)
}
func TestCacheRecentUniqueSuppressedResults_GetResultsError(t *testing.T) {
ctx := context.Background()
cfg := getMultiPrefixConfig()
wrapper := oswrapper.CreateFSTestOSWrapper()
modifiedQueryResults := getMultiPrefixQueryResults()
modifiedQueryResults["core_prefix"][0].Tags[0].Key = "non_typ_tag"
client := resultsdb.MockBigQueryClient{
RecentUniqueSuppressedReturnValues: modifiedQueryResults,
}
cfg.Querier = &client
resultsByExecutionMode, err := CacheRecentUniqueSuppressedResults(
ctx, cfg, fileutils.ThisDir(), wrapper)
require.Nil(t, resultsByExecutionMode)
require.ErrorContains(t, err,
"Got unexpected tag key non_typ_tag")
}
func TestCacheRecentUniqueSuppressedResults_Success(t *testing.T) {
ctx := context.Background()
cfg := getMultiPrefixConfig()
wrapper := oswrapper.CreateFSTestOSWrapper()
client := resultsdb.MockBigQueryClient{
RecentUniqueSuppressedReturnValues: getMultiPrefixQueryResults(),
}
cfg.Querier = &client
resultsByExecutionMode, err := CacheRecentUniqueSuppressedResults(
ctx, cfg, fileutils.ThisDir(), wrapper)
require.NoErrorf(t, err, "Error getting results: %v", err)
require.Equal(t, getExpectedMultiPrefixResults(), resultsByExecutionMode)
}
/*******************************************************************************
* getRecentUniqueSuppressedResults tests
******************************************************************************/
func TestGetRecentUniqueSuppressedResults_PrefixMismatch(t *testing.T) {
ctx := context.Background()
cfg := Config{
Tests: []TestConfig{
TestConfig{
ExecutionMode: result.ExecutionMode("execution_mode"),
Prefixes: []string{"prefix"},
},
},
}
client := resultsdb.MockBigQueryClient{
RecentUniqueSuppressedReturnValues: resultsdb.PrefixGroupedQueryResults{
"prefix": []resultsdb.QueryResult{
{
TestId: "bad_test",
Status: "FAIL",
Tags: []resultsdb.TagPair{},
Duration: 1.0,
},
},
},
}
cfg.Querier = &client
resultsByExecutionMode, err := getRecentUniqueSuppressedResults(ctx, cfg)
require.Nil(t, resultsByExecutionMode)
require.ErrorContains(t, err,
"Test ID bad_test did not start with prefix even though query should have filtered.")
}
func TestGetRecentUniqueSuppressedResults_NonTypTag(t *testing.T) {
ctx := context.Background()
cfg := Config{
Tests: []TestConfig{
TestConfig{
ExecutionMode: result.ExecutionMode("execution_mode"),
Prefixes: []string{"prefix"},
},
},
}
client := resultsdb.MockBigQueryClient{
RecentUniqueSuppressedReturnValues: resultsdb.PrefixGroupedQueryResults{
"prefix": []resultsdb.QueryResult{
{
TestId: "prefix_test",
Status: "FAIL",
Tags: []resultsdb.TagPair{
{
Key: "non_typ_tag",
Value: "value",
},
},
Duration: 1.0,
},
},
},
}
cfg.Querier = &client
resultsByExecutionMode, err := getRecentUniqueSuppressedResults(ctx, cfg)
require.Nil(t, resultsByExecutionMode)
require.ErrorContains(t, err,
"Got unexpected tag key non_typ_tag")
}
func TestGetRecentUniqueSuppressedResults_Success(t *testing.T) {
ctx := context.Background()
cfg := Config{
Tests: []TestConfig{
TestConfig{
ExecutionMode: result.ExecutionMode("execution_mode"),
Prefixes: []string{"prefix"},
},
},
}
client := resultsdb.MockBigQueryClient{
RecentUniqueSuppressedReturnValues: resultsdb.PrefixGroupedQueryResults{
"prefix": []resultsdb.QueryResult{
{
TestId: "prefix_test_1",
Status: "FAIL",
Tags: []resultsdb.TagPair{
{
Key: "typ_tag",
Value: "tag_1",
},
},
Duration: 1.0,
},
{
TestId: "prefix_test_2",
Status: "FAIL",
Tags: []resultsdb.TagPair{
{
Key: "typ_tag",
Value: "tag_1",
},
},
Duration: 1.0,
},
{
TestId: "prefix_test_1",
Status: "FAIL",
Tags: []resultsdb.TagPair{
{
Key: "typ_tag",
Value: "tag_2",
},
},
Duration: 1.0,
},
{
TestId: "prefix_test_2",
Status: "FAIL",
Tags: []resultsdb.TagPair{
{
Key: "typ_tag",
Value: "tag_2",
},
},
Duration: 1.0,
},
},
},
}
cfg.Querier = &client
expectedResults := result.ResultsByExecutionMode{
"execution_mode": result.List{
{
Query: query.Parse("_test_1"),
Tags: result.NewTags("tag_1"),
Status: result.Pass,
Duration: 0,
MayExonerate: false,
},
{
Query: query.Parse("_test_1"),
Tags: result.NewTags("tag_2"),
Status: result.Pass,
Duration: 0,
MayExonerate: false,
},
{
Query: query.Parse("_test_2"),
Tags: result.NewTags("tag_1"),
Status: result.Pass,
Duration: 0,
MayExonerate: false,
},
{
Query: query.Parse("_test_2"),
Tags: result.NewTags("tag_2"),
Status: result.Pass,
Duration: 0,
MayExonerate: false,
},
},
}
resultsByExecutionMode, err := getRecentUniqueSuppressedResults(ctx, cfg)
require.NoErrorf(t, err, "Got error getting results: %v", err)
require.Equal(t, expectedResults, resultsByExecutionMode)
}