blob: 8fb967fd8c8c891b7493fb1cdf3d82daf2467962 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"context"
"fmt"
"path"
"regexp"
"strings"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/logging"
"infra/rts"
"infra/rts/filegraph/git"
"infra/rts/presubmit/eval"
evalpb "infra/rts/presubmit/eval/proto"
)
// The range of the number of changed files to enable RTS.
// If the number of changed files is not in this range, then RTS is disabled.
// Note that 99.3% of developer-authored git commits change <= 100 files, see bit.ly/chromium-rts
const (
minChangedFiles = 1
maxChangedFiles = 100
)
// mustAlwaysRunTest returns true if the test file must never be skipped.
func mustAlwaysRunTest(testFile string) bool {
switch {
// Always run all third-party tests (never skip them),
// except //third_party/blink which is actually first party.
case strings.Contains(testFile, "/third_party/") && !strings.HasPrefix(testFile, "//third_party/blink/"):
return true
case testFile == "//third_party/blink/web_tests/wpt_internal/webgpu/cts.html":
// Most of cts.html commits are auto-generated.
// https://source.chromium.org/chromium/chromium/src/+/HEAD:third_party/blink/web_tests/wpt_internal/webgpu/cts.html;l=5;bpv=1;bpt=0
// cts.html does not have meaningful edges in the file graph.
return true
default:
return false
}
}
var (
// requireAllTests is a list of patterns of files that require running all
// tests.
requireAllTests = []string{
// A CL changes the way tests run or their configurations.
"//testing/.+",
// The full list of modified files is not available, and the
// graph does not include DEPSed file changes anyway.
"//DEPS",
// MB CLs change the way tests run or their configurations.
"//tools/mb/.+",
}
requireAllTestsRegexp = regexp.MustCompile(fmt.Sprintf("^(%s)$", strings.Join(requireAllTests, "|")))
disableRTS = errors.BoolTag{Key: errors.NewTagKey("skip RTS")}
)
// selectTests calls skipFile for test files that should be skipped.
// May return an error annotated with disableRTS tag and the message explaining
// why RTS was disabled.
func (r *selectRun) selectTests(skipFile func(*TestFile) error) (err error) {
// Disable RTS if the number of files is unusual.
if len(r.changedFiles) < minChangedFiles || len(r.changedFiles) > maxChangedFiles {
return errors.Reason(
"%d files were changed, which is outside of [%d, %d] range",
len(r.changedFiles),
minChangedFiles,
maxChangedFiles,
).Tag(disableRTS).Err()
}
// Check if any of the changed files requires all tests.
for f := range r.changedFiles {
if requireAllTestsRegexp.MatchString(f) {
return errors.Reason(
"%q was changed, which matches regexp %s",
f,
requireAllTests,
).Tag(disableRTS).Err()
}
}
r.strategy.Select(r.changedFiles.ToSlice(), func(fileName string) (keepGoing bool) {
file, ok := r.testFiles[fileName]
if !ok {
return true
}
err = skipFile(file)
return err == nil
})
return
}
func (r *createModelRun) evalStrategy(er *git.EdgeReader) eval.Strategy {
s := &git.SelectionStrategy{
Graph: r.fg,
EdgeReader: er,
OnTestNotFound: func(ctx context.Context, tv *evalpb.TestVariant) {
if strings.Contains(path.Base(tv.FileName), "autogen") {
// This file is autogenerated.
return
}
logging.Warningf(ctx, "test file not found: %s", tv.FileName)
},
}
return func(ctx context.Context, in eval.Input, out *eval.Output) error {
for _, f := range in.ChangedFiles {
switch {
case f.Repo != "https://chromium.googlesource.com/chromium/src":
return errors.Reason("unexpected repo %q", f.Repo).Err()
case requireAllTestsRegexp.MatchString(f.Path):
return nil
}
}
if err := s.SelectEval(ctx, in, out); err != nil {
return err
}
// No matter what filegraph said, never skip certain tests.
for i, tv := range in.TestVariants {
if mustAlwaysRunTest(tv.FileName) {
out.TestVariantAffectedness[i] = rts.Affectedness{Distance: 0}
}
}
return nil
}
}