mirror of
https://github.com/golang/go
synced 2024-11-02 13:42:29 +00:00
internal/dag: add a Graph type and make node order deterministic
The go/types package doesn't care about node ordering because it's just querying paths in the graph, but we're about to use this for the runtime lock graph, and there we want determinism. For #53789. Change-Id: Ic41329bf2eb9a3a202f97c21c761ea588ca551c8 Reviewed-on: https://go-review.googlesource.com/c/go/+/418593 Reviewed-by: Michael Pratt <mpratt@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Austin Clements <austin@google.com>
This commit is contained in:
parent
d37cc9a8cd
commit
426ea5702b
3 changed files with 127 additions and 21 deletions
|
@ -597,11 +597,10 @@ func TestDependencies(t *testing.T) {
|
|||
if sawImport[pkg] == nil {
|
||||
sawImport[pkg] = map[string]bool{}
|
||||
}
|
||||
ok := policy[pkg]
|
||||
var bad []string
|
||||
for _, imp := range imports {
|
||||
sawImport[pkg][imp] = true
|
||||
if !ok[imp] {
|
||||
if !policy.HasEdge(pkg, imp) {
|
||||
bad = append(bad, imp)
|
||||
}
|
||||
}
|
||||
|
@ -670,7 +669,7 @@ func findImports(pkg string) ([]string, error) {
|
|||
}
|
||||
|
||||
// depsPolicy returns a map m such that m[p][d] == true when p can import d.
|
||||
func depsPolicy(t *testing.T) map[string]map[string]bool {
|
||||
func depsPolicy(t *testing.T) *dag.Graph {
|
||||
g, err := dag.Parse(depsRules)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
@ -43,13 +43,52 @@ package dag
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Parse returns a map m such that m[p][d] == true when there is a
|
||||
// path from p to d.
|
||||
func Parse(dag string) (map[string]map[string]bool, error) {
|
||||
allowed := map[string]map[string]bool{"NONE": {}}
|
||||
type Graph struct {
|
||||
Nodes []string
|
||||
byLabel map[string]int
|
||||
edges map[string]map[string]bool
|
||||
}
|
||||
|
||||
func newGraph() *Graph {
|
||||
return &Graph{byLabel: map[string]int{}, edges: map[string]map[string]bool{}}
|
||||
}
|
||||
|
||||
func (g *Graph) addNode(label string) bool {
|
||||
if _, ok := g.byLabel[label]; ok {
|
||||
return false
|
||||
}
|
||||
g.byLabel[label] = len(g.Nodes)
|
||||
g.Nodes = append(g.Nodes, label)
|
||||
g.edges[label] = map[string]bool{}
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *Graph) AddEdge(from, to string) {
|
||||
g.edges[from][to] = true
|
||||
}
|
||||
|
||||
func (g *Graph) HasEdge(from, to string) bool {
|
||||
return g.edges[from] != nil && g.edges[from][to]
|
||||
}
|
||||
|
||||
func (g *Graph) Edges(from string) []string {
|
||||
edges := make([]string, 0, 16)
|
||||
for k := range g.edges[from] {
|
||||
edges = append(edges, k)
|
||||
}
|
||||
sort.Slice(edges, func(i, j int) bool { return g.byLabel[edges[i]] < g.byLabel[edges[j]] })
|
||||
return edges
|
||||
}
|
||||
|
||||
// Parse parses the DAG language and returns the transitive closure of
|
||||
// the described graph. In the returned graph, there is an edge from "b"
|
||||
// to "a" if b < a (or a > b) in the partial order.
|
||||
func Parse(dag string) (*Graph, error) {
|
||||
g := newGraph()
|
||||
disallowed := []rule{}
|
||||
|
||||
rules, err := parseRules(dag)
|
||||
|
@ -68,40 +107,47 @@ func Parse(dag string) (map[string]map[string]bool, error) {
|
|||
continue
|
||||
}
|
||||
for _, def := range r.def {
|
||||
if allowed[def] != nil {
|
||||
if def == "NONE" {
|
||||
errorf("NONE cannot be a predecessor")
|
||||
continue
|
||||
}
|
||||
if !g.addNode(def) {
|
||||
errorf("multiple definitions for %s", def)
|
||||
}
|
||||
allowed[def] = make(map[string]bool)
|
||||
for _, less := range r.less {
|
||||
if allowed[less] == nil {
|
||||
errorf("use of %s before its definition", less)
|
||||
if less == "NONE" {
|
||||
continue
|
||||
}
|
||||
if _, ok := g.byLabel[less]; !ok {
|
||||
errorf("use of %s before its definition", less)
|
||||
} else {
|
||||
g.AddEdge(def, less)
|
||||
}
|
||||
allowed[def][less] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for missing definition.
|
||||
for _, tos := range allowed {
|
||||
for _, tos := range g.edges {
|
||||
for to := range tos {
|
||||
if allowed[to] == nil {
|
||||
if g.edges[to] == nil {
|
||||
errorf("missing definition for %s", to)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Complete transitive closure.
|
||||
for k := range allowed {
|
||||
for i := range allowed {
|
||||
for j := range allowed {
|
||||
if i != k && k != j && allowed[i][k] && allowed[k][j] {
|
||||
for _, k := range g.Nodes {
|
||||
for _, i := range g.Nodes {
|
||||
for _, j := range g.Nodes {
|
||||
if i != k && k != j && g.HasEdge(i, k) && g.HasEdge(k, j) {
|
||||
if i == j {
|
||||
// Can only happen along with a "use of X before deps" error above,
|
||||
// but this error is more specific - it makes clear that reordering the
|
||||
// rules will not be enough to fix the problem.
|
||||
errorf("graph cycle: %s < %s < %s", j, k, i)
|
||||
}
|
||||
allowed[i][j] = true
|
||||
g.AddEdge(i, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +157,7 @@ func Parse(dag string) (map[string]map[string]bool, error) {
|
|||
for _, bad := range disallowed {
|
||||
for _, less := range bad.less {
|
||||
for _, def := range bad.def {
|
||||
if allowed[def][less] {
|
||||
if g.HasEdge(def, less) {
|
||||
errorf("graph edge assertion failed: %s !< %s", less, def)
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +168,7 @@ func Parse(dag string) (map[string]map[string]bool, error) {
|
|||
return nil, fmt.Errorf("%s", strings.Join(errors, "\n"))
|
||||
}
|
||||
|
||||
return allowed, nil
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// A rule is a line in the DAG language where "less < def" or "less !< def".
|
||||
|
|
61
src/internal/dag/parse_test.go
Normal file
61
src/internal/dag/parse_test.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2022 The Go 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 dag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const diamond = `
|
||||
NONE < a < b, c < d;
|
||||
`
|
||||
|
||||
func mustParse(t *testing.T, dag string) *Graph {
|
||||
t.Helper()
|
||||
g, err := Parse(dag)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
func wantEdges(t *testing.T, g *Graph, edges string) {
|
||||
t.Helper()
|
||||
|
||||
wantEdges := strings.Fields(edges)
|
||||
wantEdgeMap := make(map[string]bool)
|
||||
for _, e := range wantEdges {
|
||||
wantEdgeMap[e] = true
|
||||
}
|
||||
|
||||
for _, n1 := range g.Nodes {
|
||||
for _, n2 := range g.Nodes {
|
||||
got := g.HasEdge(n1, n2)
|
||||
want := wantEdgeMap[n1+"->"+n2]
|
||||
if got && want {
|
||||
t.Logf("%s->%s", n1, n2)
|
||||
} else if got && !want {
|
||||
t.Errorf("%s->%s present but not expected", n1, n2)
|
||||
} else if want && !got {
|
||||
t.Errorf("%s->%s missing but expected", n1, n2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
// Basic smoke test for graph parsing.
|
||||
g := mustParse(t, diamond)
|
||||
|
||||
wantNodes := strings.Fields("a b c d")
|
||||
if !reflect.DeepEqual(wantNodes, g.Nodes) {
|
||||
t.Fatalf("want nodes %v, got %v", wantNodes, g.Nodes)
|
||||
}
|
||||
|
||||
// Parse returns the transitive closure, so it adds d->a.
|
||||
wantEdges(t, g, "b->a c->a d->a d->b d->c")
|
||||
}
|
Loading…
Reference in a new issue