blob: 909d338e7cc9901fcba040f03c41a586e373eaae [file] [log] [blame]
// Copyright 2024 The LUCI 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 lowlatency
import (
"fmt"
"time"
"go.chromium.org/luci/analysis/internal/testresults"
pb "go.chromium.org/luci/analysis/proto/v1"
)
// TestResultBuilder provides methods to build a test result for testing.
type TestResultBuilder struct {
result TestResult
}
func NewTestResult() TestResultBuilder {
result := TestResult{
Project: "proj",
TestID: "test_id",
VariantHash: "hash",
Sources: testresults.Sources{
RefHash: []byte{0, 1, 2, 3, 4, 5, 6, 7},
Position: 999444,
Changelists: []testresults.Changelist{
{
Host: "mygerrit-review.googlesource.com",
Change: 12345678,
Patchset: 9,
},
{
Host: "anothergerrit.gerrit.instance",
Change: 234568790,
Patchset: 1,
},
},
IsDirty: true,
},
RootInvocationID: "root-inv-id",
InvocationID: "inv-id",
ResultID: "result-id",
PartitionTime: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC),
SubRealm: "realm",
IsUnexpected: true,
Status: pb.TestResultStatus_PASS,
}
return TestResultBuilder{
result: result,
}
}
func (b TestResultBuilder) WithProject(project string) TestResultBuilder {
b.result.Project = project
return b
}
func (b TestResultBuilder) WithTestID(testID string) TestResultBuilder {
b.result.TestID = testID
return b
}
func (b TestResultBuilder) WithVariantHash(variantHash string) TestResultBuilder {
b.result.VariantHash = variantHash
return b
}
func (b TestResultBuilder) WithSources(sources testresults.Sources) TestResultBuilder {
// Copy sources to avoid aliasing artifacts. Changes made to
// slices within the sources struct after this call should
// not propagate to the test result.
b.result.Sources = testresults.CopySources(sources)
return b
}
func (b TestResultBuilder) WithRootInvocationID(rootInvID string) TestResultBuilder {
b.result.RootInvocationID = rootInvID
return b
}
func (b TestResultBuilder) WithInvocationID(invID string) TestResultBuilder {
b.result.InvocationID = invID
return b
}
func (b TestResultBuilder) WithResultID(resultID string) TestResultBuilder {
b.result.ResultID = resultID
return b
}
// WithPartitionTime specifies the partition time of the test result.
func (b TestResultBuilder) WithPartitionTime(partitionTime time.Time) TestResultBuilder {
b.result.PartitionTime = partitionTime
return b
}
// WithSubRealm specifies the subrealm of the test result.
func (b TestResultBuilder) WithSubRealm(subRealm string) TestResultBuilder {
b.result.SubRealm = subRealm
return b
}
// WithIsUnexpected specifies whether the test result is unexpected.
func (b TestResultBuilder) WithIsUnexpected(isUnexpected bool) TestResultBuilder {
b.result.IsUnexpected = isUnexpected
return b
}
// WithStatus specifies the status of the test result.
func (b TestResultBuilder) WithStatus(status pb.TestResultStatus) TestResultBuilder {
b.result.Status = status
return b
}
func (b TestResultBuilder) Build() *TestResult {
// Copy the result, so that calling further methods on the builder does
// not change the returned test verdict.
result := new(TestResult)
*result = b.result
result.Sources = testresults.CopySources(b.result.Sources)
return result
}
// TestVerdictBuilder provides methods to build a test variant for testing.
type TestVerdictBuilder struct {
baseResult TestResult
runStatuses []RunStatus
}
type RunStatus int64
const (
Unexpected RunStatus = iota
Flaky
Expected
)
func NewTestVerdict() *TestVerdictBuilder {
result := new(TestVerdictBuilder)
result.baseResult = *NewTestResult().WithStatus(pb.TestResultStatus_PASS).Build()
result.runStatuses = nil
return result
}
// WithBaseTestResult specifies a test result to use as the template for
// the test variant's test results.
func (b *TestVerdictBuilder) WithBaseTestResult(testResult *TestResult) *TestVerdictBuilder {
b.baseResult = *testResult
return b
}
// WithRunStatus specifies the status of runs of the test verdict.
func (b *TestVerdictBuilder) WithRunStatus(runStatuses ...RunStatus) *TestVerdictBuilder {
b.runStatuses = runStatuses
return b
}
// applyRunStatus applies the given run status to the given test results.
func applyRunStatus(trs []*TestResult, runStatus RunStatus) {
for _, tr := range trs {
tr.IsUnexpected = true
}
switch runStatus {
case Expected:
for _, tr := range trs {
tr.IsUnexpected = false
}
case Flaky:
trs[0].IsUnexpected = false
case Unexpected:
// All test results already unexpected.
}
}
func (b *TestVerdictBuilder) Build() []*TestResult {
runs := 2
if len(b.runStatuses) > 0 {
runs = len(b.runStatuses)
}
// Create two test results per run, to allow
// for all expected, all unexpected and
// flaky (mixed expected+unexpected) statuses
// to be represented.
trs := make([]*TestResult, 0, runs*2)
for i := 0; i < runs*2; i++ {
tr := new(TestResult)
*tr = b.baseResult
tr.InvocationID = fmt.Sprintf("child-%s-run-%v", b.baseResult.RootInvocationID, i/2)
tr.ResultID = fmt.Sprintf("result-%v", i%2)
trs = append(trs, tr)
}
for i, runStatus := range b.runStatuses {
runTRs := trs[i*2 : (i+1)*2]
applyRunStatus(runTRs, runStatus)
}
return trs
}