blob: 87c4687b1307a481aa4c6b75a6e7a8f8cf6d97cf [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 affectedtests
import (
"container/list"
"fmt"
"io"
"sort"
"strings"
"go.fuchsia.dev/fuchsia/tools/build"
"go.fuchsia.dev/fuchsia/tools/build/ninjago/ninjagraph"
)
const buildDirRelativePrefix = "../../"
type void struct{}
type stringSet map[string]void
type intSet map[int64]void
var member void
// AffectedTests finds tests out of `testSpecs` that are affected by changes to `srcs` according to the build graph `dotBytes`.
func AffectedTests(srcs []string, testSpecs []build.TestSpec, dotFile io.Reader) ([]string, error) {
graph, err := ninjagraph.FromDOT(dotFile)
if err != nil {
return nil, err
}
testLabelToName := make(map[string][]string)
for _, testSpec := range testSpecs {
test := testSpec.Test
label := test.Label
testLabelToName[label] = append(testLabelToName[label], test.Name)
}
testStampToNames := make(map[string][]string)
for label, names := range testLabelToName {
// //this/is/a:label(//some:toolchain) -> obj/this/is/a/label.stamp
stamp := "obj/" + strings.Replace(label[2:strings.IndexByte(label, '(')], ":", "/", 1) + ".stamp"
testStampToNames[stamp] = names
}
srcsSet := make(stringSet)
for _, src := range srcs {
srcsSet[src] = member
}
nodesToVisit := list.New()
for id, node := range graph.Nodes {
// Cut leading "../../" to rebase output to root build dir
if !strings.HasPrefix(node.Path, buildDirRelativePrefix) {
continue
}
if _, exists := srcsSet[node.Path[len(buildDirRelativePrefix):]]; exists {
nodesToVisit.PushBack(id)
}
}
nodesVisited := make(intSet)
for e := nodesToVisit.Front(); e != nil; e = e.Next() {
var nodeID int64 = e.Value.(int64)
nodesVisited[nodeID] = member
node := graph.Nodes[nodeID]
if node == nil {
return nil, fmt.Errorf("node %x not found in graph, this indicates a bug in graph traversal", nodeID)
}
for _, edge := range node.Outs {
for _, outputNodeID := range edge.Outputs {
if _, exists := nodesVisited[outputNodeID]; !exists {
nodesToVisit.PushBack(outputNodeID)
}
}
}
}
affectedTestStamps := make(stringSet)
for nodeID := range nodesVisited {
node := graph.Nodes[nodeID]
if node == nil {
return nil, fmt.Errorf("node %x not found in graph, this indicates a bug in graph traversal", nodeID)
}
if _, exists := testStampToNames[node.Path]; exists {
affectedTestStamps[node.Path] = member
}
}
affectedTestNames := []string{}
for stamp := range affectedTestStamps {
affectedTestNames = append(affectedTestNames, testStampToNames[stamp]...)
}
sort.Strings(affectedTestNames)
return affectedTestNames, nil
}