| // 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 |
| } |