[tools][cts] Add results test coverage
Makes the following changes:
* Renames results.CleanTags() to results.CleanResults() since it
modifies more than just tags.
* Adds test coverage for results.GetResults()
* Adds test coverage for results.CleanResults()
Bug: chromium:342554800
Change-Id: I348e75a1b64d787181ef6fecbbc2dab2e7a2fb44
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/190565
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Auto-Submit: Brian Sheedy <bsheedy@google.com>
diff --git a/tools/src/cmd/cts/common/results.go b/tools/src/cmd/cts/common/results.go
index 21e2a34..bc50695 100644
--- a/tools/src/cmd/cts/common/results.go
+++ b/tools/src/cmd/cts/common/results.go
@@ -154,10 +154,10 @@
}
// CacheResults looks in the cache at 'cacheDir' for the results for the given patchset.
-// If the cache contains the results, then these are loaded, transformed with CleanTags() and
+// If the cache contains the results, then these are loaded, transformed with CleanResults() and
// returned.
// If the cache does not contain the results, then they are fetched using GetRawResults(), saved to
-// the cache directory, transformed with CleanTags() and then returned.
+// the cache directory, transformed with CleanResults() and then returned.
func CacheResults(
ctx context.Context,
cfg Config,
@@ -189,7 +189,7 @@
// Expand aliased tags, remove specific tags
for i, results := range resultsByExecutionMode {
- CleanTags(cfg, &results)
+ CleanResults(cfg, &results)
results.Sort()
resultsByExecutionMode[i] = results
}
@@ -197,7 +197,7 @@
return resultsByExecutionMode, nil
}
-// GetResults calls GetRawResults() to fetch the build results from ResultDB and applies CleanTags().
+// GetResults calls GetRawResults() to fetch the build results from ResultDB and applies CleanResults().
// GetResults does not trigger new builds.
func GetResults(
ctx context.Context,
@@ -212,7 +212,7 @@
// Expand aliased tags, remove specific tags
for i, results := range resultsByExecutionMode {
- CleanTags(cfg, &results)
+ CleanResults(cfg, &results)
results.Sort()
resultsByExecutionMode[i] = results
}
@@ -220,7 +220,7 @@
return resultsByExecutionMode, err
}
-// GetRawResults fetches the build results from ResultDB, without applying CleanTags().
+// GetRawResults fetches the build results from ResultDB, without applying CleanResults().
// GetRawResults does not trigger new builds.
func GetRawResults(
ctx context.Context,
@@ -383,10 +383,10 @@
return nil, gerrit.Patchset{}, fmt.Errorf("no builds found for change %v", change)
}
-// CleanTags modifies each result so that tags in cfg.Tag.Remove are removed and
+// CleanResults modifies each result so that tags in cfg.Tag.Remove are removed and
// duplicate results are removed by erring towards Failure.
// See: crbug.com/dawn/1387, crbug.com/dawn/1401
-func CleanTags(cfg Config, results *result.List) {
+func CleanResults(cfg Config, results *result.List) {
// Remove any tags found in cfg.Tag.Remove
remove := result.NewTags(cfg.Tag.Remove...)
for _, r := range *results {
diff --git a/tools/src/cmd/cts/common/results_test.go b/tools/src/cmd/cts/common/results_test.go
index 77ce620..5e041f6 100644
--- a/tools/src/cmd/cts/common/results_test.go
+++ b/tools/src/cmd/cts/common/results_test.go
@@ -39,6 +39,13 @@
"github.com/stretchr/testify/assert"
)
+// TODO(crbug.com/342554800): Add test coverage for:
+// ResultSource.GetResults (requires breaking out into helper functions)
+// CacheResults
+// LatestCTSRoll
+// LatestPatchset
+// MostRecentResultsForChange
+
/*******************************************************************************
* Fake implementations
******************************************************************************/
@@ -59,10 +66,10 @@
}
/*******************************************************************************
- * GetRawResults tests
+ * GetResults tests
******************************************************************************/
-func generateGoodGetRawResultsInputs() (context.Context, Config, *mockedBigQueryClient, BuildsByName) {
+func generateGoodGetResultsInputs() (context.Context, Config, *mockedBigQueryClient, BuildsByName) {
ctx := context.Background()
cfg := Config{
@@ -90,9 +97,85 @@
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.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, client, builds)
+ assert.Nil(t, err)
+ assert.Equal(t, results, expectedResults)
+}
+
+// Tests that errors from GetRawResults are properly surfaced.
+func TestGetResultsGetRawResultsErrorSurfaced(t *testing.T) {
+ ctx, cfg, client, builds := generateGoodGetResultsInputs()
+ client.returnValues[0].TestId = "bad_test"
+
+ results, err := GetResults(ctx, cfg, client, builds)
+ assert.Nil(t, results)
+ assert.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 := generateGoodGetRawResultsInputs()
+ ctx, cfg, client, builds := generateGoodGetResultsInputs()
client.returnValues = []resultsdb.QueryResult{
resultsdb.QueryResult{
TestId: "prefix_test_1",
@@ -180,7 +263,7 @@
// Tests that a mismatched prefix results in an error.
func TestGetRawResultsPrefixMismatch(t *testing.T) {
- ctx, cfg, client, builds := generateGoodGetRawResultsInputs()
+ ctx, cfg, client, builds := generateGoodGetResultsInputs()
client.returnValues[0].TestId = "bad_test"
results, err := GetRawResults(ctx, cfg, client, builds)
@@ -190,7 +273,7 @@
// Tests that a JavaScript duration that cannot be parsed results in an error.
func TestGetRawResultsBadJavaScriptDuration(t *testing.T) {
- ctx, cfg, client, builds := generateGoodGetRawResultsInputs()
+ ctx, cfg, client, builds := generateGoodGetResultsInputs()
client.returnValues[0].Tags = []resultsdb.TagPair{
resultsdb.TagPair{
Key: "javascript_duration",
@@ -205,7 +288,7 @@
// Tests that a non-boolean may_exonerate value results in an error.
func TestGetRawResultsBadMayExonerate(t *testing.T) {
- ctx, cfg, client, builds := generateGoodGetRawResultsInputs()
+ ctx, cfg, client, builds := generateGoodGetResultsInputs()
client.returnValues[0].Tags = []resultsdb.TagPair{
resultsdb.TagPair{
Key: "may_exonerate",
@@ -217,3 +300,194 @@
assert.Nil(t, results)
assert.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)
+ assert.Equal(t, results, expectedResults)
+}
+
+// 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)
+ assert.Equal(t, results, expectedResults)
+ }
+}
+
+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)
+ assert.Equal(t, results, expectedResults)
+ }
+}
+
+// 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)
+ assert.Equal(t, results, expectedResults)
+ }
+ }
+}