blob: a9989160c46ddd8b191fe97c1e90202ab3dae593 [file] [log] [blame]
// Copyright 2021 The Chromium OS 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 generator
import (
"fmt"
"log"
"infra/cros/internal/match"
"go.chromium.org/chromiumos/infra/proto/go/testplans"
)
type testType int
type testGroup string
const (
hw testType = iota
vm
)
var (
testTypeFilter = map[testType]func(testReqs *testplans.SourceTestRules) bool{
hw: func(testReqs *testplans.SourceTestRules) bool {
return testReqs.GetSubtractiveRule().GetDisableHwTests()
},
vm: func(testReqs *testplans.SourceTestRules) bool {
return testReqs.GetSubtractiveRule().GetDisableVmTests()
},
}
)
func (tt testType) String() string {
return [...]string{"Hw", "Vm"}[tt]
}
type testPruneResult struct {
disableHWTests bool
disableVMTests bool
onlyKeepAllSuitesInGroups map[testGroup]bool
onlyKeepOneSuiteFromEachGroup map[testGroup]bool
addAllSuitesInGroups map[testGroup]bool
addOneSuiteFromEachGroup map[testGroup]bool
}
func (tpr testPruneResult) hasOnlyKeepSuiteRules() bool {
return len(tpr.onlyKeepOneSuiteFromEachGroup) > 0 || len(tpr.onlyKeepAllSuitesInGroups) > 0
}
func (tpr testPruneResult) hasAddAllOrOneTestRules() bool {
return len(tpr.addOneSuiteFromEachGroup) > 0 || len(tpr.addAllSuitesInGroups) > 0
}
func (tpr testPruneResult) canSkipForOnlyTestRule(groups []*testplans.TestSuiteCommon_TestSuiteGroup) bool {
// If the source config didn't specify any onlyKeepAllSuitesInGroups, we can't skip testing for the groups in the params.
if len(tpr.onlyKeepAllSuitesInGroups) == 0 {
return false
}
for _, g := range groups {
if tpr.onlyKeepAllSuitesInGroups[testGroup(g.TestSuiteGroup)] {
return false
}
}
return true
}
func (tpr testPruneResult) mustAddForAlsoTestRule(groups []*testplans.TestSuiteCommon_TestSuiteGroup) bool {
for _, g := range groups {
if tpr.addAllSuitesInGroups[testGroup(g.TestSuiteGroup)] {
return true
}
}
return false
}
func extractPruneResult(
sourceTreeCfg *testplans.SourceTreeTestCfg,
srcPaths []string) (*testPruneResult, error) {
result := &testPruneResult{}
if len(srcPaths) == 0 {
// This happens during postsubmit runs, for example.
log.Print("no gerrit_changes, so no tests will be skipped")
return result, nil
}
disableHW := true
disableHWLoop:
for _, fileSrcPath := range srcPaths {
disableHWForPath, err := canDisableTestingForPath(fileSrcPath, sourceTreeCfg, hw)
if err != nil {
return result, err
}
if !disableHWForPath {
log.Printf("cannot disable HW testing due to file %s", fileSrcPath)
disableHW = false
break disableHWLoop
}
}
disableVM := true
disableVMLoop:
for _, fileSrcPath := range srcPaths {
disableVMForPath, err := canDisableTestingForPath(fileSrcPath, sourceTreeCfg, vm)
if err != nil {
return result, err
}
if !disableVMForPath {
log.Printf("cannot disable VM testing due to file %s", fileSrcPath)
disableVM = false
break disableVMLoop
}
}
onlyKeepAllSuitesInGroups := make(map[testGroup]bool)
onlyKeepOneSuiteFromEachGroup := make(map[testGroup]bool)
fileLoop:
for _, fileSrcPath := range srcPaths {
fileOnlyTestGroups, err := getOnlyTestGroups(fileSrcPath, sourceTreeCfg)
if err != nil {
return result, err
}
fileOneofTestGroups, err := getOneofTestGroups(fileSrcPath, sourceTreeCfg)
if err != nil {
return result, err
}
if len(fileOnlyTestGroups) == 0 && len(fileOneofTestGroups) == 0 {
log.Printf("cannot use subtractive test group rules on set of builders for testing due to %s", fileSrcPath)
onlyKeepAllSuitesInGroups = make(map[testGroup]bool)
onlyKeepOneSuiteFromEachGroup = make(map[testGroup]bool)
break fileLoop
} else {
for g, include := range fileOnlyTestGroups {
onlyKeepAllSuitesInGroups[g] = include
}
for g, include := range fileOneofTestGroups {
onlyKeepOneSuiteFromEachGroup[g] = include
}
}
}
addAllSuitesInGroups := make(map[testGroup]bool)
addOneSuiteFromEachGroup := make(map[testGroup]bool)
for _, fileSrcPath := range srcPaths {
fileAlsoTestGroups, err := getAddAllSuitesInGroups(fileSrcPath, sourceTreeCfg)
if err != nil {
return result, err
}
for k, v := range fileAlsoTestGroups {
addAllSuitesInGroups[k] = v
log.Printf("Will also test testGroup %v due to file %v", k, fileSrcPath)
}
fileAddOneSuiteFromEachGroup, err := getAddOneSuiteFromEachGroup(fileSrcPath, sourceTreeCfg)
if err != nil {
return result, err
}
for k, v := range fileAddOneSuiteFromEachGroup {
addOneSuiteFromEachGroup[k] = v
log.Printf("Will also test one suite from testGroup %v due to file %v", k, fileSrcPath)
}
}
return &testPruneResult{
disableHWTests: disableHW,
disableVMTests: disableVM,
onlyKeepAllSuitesInGroups: onlyKeepAllSuitesInGroups,
onlyKeepOneSuiteFromEachGroup: onlyKeepOneSuiteFromEachGroup,
addAllSuitesInGroups: addAllSuitesInGroups,
addOneSuiteFromEachGroup: addOneSuiteFromEachGroup},
nil
}
func getOnlyTestGroups(
sourcePath string,
sourceTreeCfg *testplans.SourceTreeTestCfg) (map[testGroup]bool, error) {
onlyTestGroups := make(map[testGroup]bool)
for _, str := range sourceTreeCfg.GetSourceTestRules() {
match, err := match.FilePatternMatches(str.GetFilePattern(), sourcePath)
if err != nil {
return onlyTestGroups, err
}
if match {
okasig := str.GetSubtractiveRule().GetOnlyKeepAllSuitesInGroups()
for _, o := range okasig.GetName() {
onlyTestGroups[testGroup(o)] = true
}
}
}
return onlyTestGroups, nil
}
// getOneofTestGroups extracts rules from config about any type of oneof testing
// that can be done for the provided path. For each of the keys in the returned
// map, at least one test suite must be tested.
func getOneofTestGroups(
sourcePath string,
sourceTreeCfg *testplans.SourceTreeTestCfg) (map[testGroup]bool, error) {
oneofTestGroups := make(map[testGroup]bool)
for _, str := range sourceTreeCfg.GetSourceTestRules() {
match, err := match.FilePatternMatches(str.GetFilePattern(), sourcePath)
if err != nil {
return oneofTestGroups, err
}
okosfeg := str.GetSubtractiveRule().GetOnlyKeepOneSuiteFromEachGroup()
if match {
for _, g := range okosfeg.GetName() {
oneofTestGroups[testGroup(g)] = true
}
}
}
return oneofTestGroups, nil
}
func getAddAllSuitesInGroups(
sourcePath string,
sourceTreeCfg *testplans.SourceTreeTestCfg) (map[testGroup]bool, error) {
alsoTestGroups := make(map[testGroup]bool)
for _, str := range sourceTreeCfg.GetSourceTestRules() {
match, err := match.FilePatternMatches(str.GetFilePattern(), sourcePath)
if err != nil {
return alsoTestGroups, err
}
if match {
aasig := str.GetAdditiveRule().GetAddAllSuitesInGroups()
for _, g := range aasig.GetName() {
alsoTestGroups[testGroup(g)] = true
}
}
}
return alsoTestGroups, nil
}
func getAddOneSuiteFromEachGroup(
sourcePath string,
sourceTreeCfg *testplans.SourceTreeTestCfg) (map[testGroup]bool, error) {
alsoTestOneofEachGroup := make(map[testGroup]bool)
for _, str := range sourceTreeCfg.GetSourceTestRules() {
match, err := match.FilePatternMatches(str.GetFilePattern(), sourcePath)
if err != nil {
return alsoTestOneofEachGroup, err
}
if match {
aosfeg := str.GetAdditiveRule().GetAddOneSuiteFromEachGroup()
for _, g := range aosfeg.GetName() {
alsoTestOneofEachGroup[testGroup(g)] = true
}
}
}
return alsoTestOneofEachGroup, nil
}
// canDisableTestingForPath determines whether a particular testing type is unnecessary for
// a given file, based on source tree test restrictions.
func canDisableTestingForPath(sourcePath string, sourceTreeCfg *testplans.SourceTreeTestCfg, tt testType) (bool, error) {
for _, str := range sourceTreeCfg.GetSourceTestRules() {
testFilter, ok := testTypeFilter[tt]
if !ok {
return false, fmt.Errorf("Missing test filter for %v", tt)
}
if testFilter(str) {
match, err := match.FilePatternMatches(str.GetFilePattern(), sourcePath)
if err != nil {
return false, err
}
if match {
return true, nil
}
}
}
return false, nil
}