├── go.sum ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── checks.yml │ └── pr-auditor.yml ├── go.mod ├── Makefile ├── CODEOWNERS ├── dag ├── edge_test.go ├── edge.go ├── dot_test.go ├── tarjan_test.go ├── marshal_test.go ├── set.go ├── tarjan.go ├── set_test.go ├── graph_test.go ├── marshal.go ├── walk_test.go ├── dot.go ├── graph.go ├── dag.go ├── dag_test.go └── walk.go ├── .editorconfig ├── main.go ├── README.md └── LICENSE /go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Test plan 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sourcegraph/tf-dag 2 | 3 | go 1.23.4 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: fmt check 2 | 3 | fmt: 4 | @go mod tidy 5 | @go fmt ./... 6 | 7 | check: 8 | @go vet ./... 9 | @go test -v ./... 10 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Docs: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 2 | 3 | # These owners will be the default owners for everything in the repo. 4 | * @sourcegraph/cloud 5 | -------------------------------------------------------------------------------- /dag/edge_test.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBasicEdgeHashcode(t *testing.T) { 8 | e1 := BasicEdge(1, 2) 9 | e2 := BasicEdge(1, 2) 10 | if e1.Hashcode() != e2.Hashcode() { 11 | t.Fatalf("bad") 12 | } 13 | } 14 | 15 | func TestBasicEdgeHashcode_pointer(t *testing.T) { 16 | type test struct { 17 | Value string 18 | } 19 | 20 | v1, v2 := &test{"foo"}, &test{"bar"} 21 | e1 := BasicEdge(v1, v2) 22 | e2 := BasicEdge(v1, v2) 23 | if e1.Hashcode() != e2.Hashcode() { 24 | t.Fatalf("bad") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | charset = utf-8 4 | end_of_line = lf 5 | indent_size = 4 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | tab_width = 4 10 | 11 | [*.{json,md,yaml,yml}] 12 | indent_size = 2 13 | 14 | [Makefile] 15 | indent_style = tab 16 | 17 | [{*.go,*.go2}] 18 | indent_style = tab 19 | ij_continuation_indent_size = 4 20 | ij_go_group_stdlib_imports = true 21 | ij_go_import_sorting = goimports 22 | ij_go_move_all_imports_in_one_declaration = true 23 | ij_go_move_all_stdlib_imports_in_one_group = true 24 | ij_go_remove_redundant_import_aliases = true 25 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: checks 2 | 3 | on: [push] 4 | 5 | jobs: 6 | 7 | go: 8 | name: go checks 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-go@v3 13 | with: 14 | go-version: '1.23.4' 15 | cache: true 16 | 17 | - run: make fmt 18 | - name: Verify no changes 19 | run: | 20 | if [ -n "$(git status --porcelain)" ]; then 21 | git diff 22 | echo "::error::go mod tidy or go fmt needs to be run and the results committed" 23 | exit 1 24 | fi 25 | - run: make check 26 | 27 | -------------------------------------------------------------------------------- /dag/edge.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | // Edge represents an edge in the graph, with a source and target vertex. 4 | type Edge interface { 5 | Source() Vertex 6 | Target() Vertex 7 | 8 | Hashable 9 | } 10 | 11 | // BasicEdge returns an Edge implementation that simply tracks the source 12 | // and target given as-is. 13 | func BasicEdge(source, target Vertex) Edge { 14 | return &basicEdge{S: source, T: target} 15 | } 16 | 17 | // basicEdge is a basic implementation of Edge that has the source and 18 | // target vertex. 19 | type basicEdge struct { 20 | S, T Vertex 21 | } 22 | 23 | func (e *basicEdge) Hashcode() interface{} { 24 | return [...]interface{}{e.S, e.T} 25 | } 26 | 27 | func (e *basicEdge) Source() Vertex { 28 | return e.S 29 | } 30 | 31 | func (e *basicEdge) Target() Vertex { 32 | return e.T 33 | } 34 | -------------------------------------------------------------------------------- /dag/dot_test.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestGraphDot_opts(t *testing.T) { 9 | var v testDotVertex 10 | var g Graph 11 | g.Add(&v) 12 | 13 | opts := &DotOpts{MaxDepth: 42} 14 | actual := g.Dot(opts) 15 | if len(actual) == 0 { 16 | t.Fatal("should not be empty") 17 | } 18 | 19 | if !v.DotNodeCalled { 20 | t.Fatal("should call DotNode") 21 | } 22 | if !reflect.DeepEqual(v.DotNodeOpts, opts) { 23 | t.Fatalf("bad; %#v", v.DotNodeOpts) 24 | } 25 | } 26 | 27 | type testDotVertex struct { 28 | DotNodeCalled bool 29 | DotNodeTitle string 30 | DotNodeOpts *DotOpts 31 | DotNodeReturn *DotNode 32 | } 33 | 34 | func (v *testDotVertex) DotNode(title string, opts *DotOpts) *DotNode { 35 | v.DotNodeCalled = true 36 | v.DotNodeTitle = title 37 | v.DotNodeOpts = opts 38 | return v.DotNodeReturn 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/pr-auditor.yml: -------------------------------------------------------------------------------- 1 | # See https://docs.sourcegraph.com/dev/background-information/ci#pr-auditor 2 | name: pr-auditor 3 | on: 4 | pull_request_target: 5 | types: [ closed, edited, opened, synchronize, ready_for_review ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | check-pr: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | repository: 'sourcegraph/devx-service' 15 | token: ${{ secrets.PR_AUDITOR_TOKEN }} 16 | - uses: actions/setup-go@v4 17 | with: { go-version: '1.22' } 18 | 19 | - run: 'go run ./cmd/pr-auditor' 20 | env: 21 | GITHUB_EVENT_PATH: ${{ env.GITHUB_EVENT_PATH }} 22 | GITHUB_TOKEN: ${{ secrets.PR_AUDITOR_TOKEN }} 23 | GITHUB_RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} 24 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/sourcegraph/tf-dag/dag" 7 | ) 8 | 9 | func main() { 10 | g := buildGraph() 11 | if err := g.Walk(func(v dag.Vertex) error { 12 | fmt.Printf("visiting %d\n", v) 13 | return nil 14 | }); err != nil { 15 | fmt.Printf("error walking dag: %s", err.Error()) 16 | } 17 | 18 | g = buildGraph() 19 | if err := g.Walk(func(v dag.Vertex) error { 20 | return fmt.Errorf("error walking: %d", v) 21 | }); err != nil { 22 | fmt.Printf("error walking dag: %s", err.Error()) 23 | } 24 | } 25 | 26 | // buildGraph builds a simple graph where 27 | // some vertices could be run in parallel 28 | func buildGraph() dag.AcyclicGraph { 29 | var g dag.AcyclicGraph 30 | g.Add(1) 31 | g.Add(2) 32 | g.Add(3) 33 | g.Add(4) 34 | g.Connect(dag.BasicEdge(1, 2)) 35 | g.Connect(dag.BasicEdge(1, 3)) 36 | g.Connect(dag.BasicEdge(1, 4)) 37 | 38 | return g 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tf-dag 2 | 3 | Fork of [github.com/terraform/hashicorp/internal/dag](https://github.com/hashicorp/terraform/tree/main/internal/dag). Directed acyclic graph (DAG) implementation in Go. 4 | 5 | ## Why the fork? 6 | 7 | The `dag` package from [terraform/hashicorp] is one of the most well maintained and tested DAG implementations in Go, and [many projects depend on it](https://sourcegraph.com/search?q=context:global+lang:Go+%22github.com/hashicorp/terraform/dag%22&patternType=standard&sm=1&groupBy=repo). However, it was [made internal](https://github.com/hashicorp/terraform/commit/70eebe3521d2f1ffcb5c12b75e90f1b82db94551). Additionally, upstream package contains a lot of extra dependencies where most projects do not need. 8 | 9 | By forking it, we can import it as a standlone package and introduce custom changes to make it more generic. 10 | 11 | ### Changes 12 | 13 | Below is a list of changes we made to the upstream package: 14 | 15 | - [x] replace the use of [`tfdiags`](https://github.com/hashicorp/terraform/tree/main/internal/tfdiags) with `error` 16 | - [ ] remove logging 17 | 18 | ## Usage 19 | 20 | ```sh 21 | go get github.com/sourcegraph/tf-dag 22 | ``` 23 | 24 | [terraform/hashicorp]: https://github.com/hashicorp/terraform 25 | -------------------------------------------------------------------------------- /dag/tarjan_test.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | import ( 4 | "sort" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestGraphStronglyConnected(t *testing.T) { 10 | var g Graph 11 | g.Add(1) 12 | g.Add(2) 13 | g.Connect(BasicEdge(1, 2)) 14 | g.Connect(BasicEdge(2, 1)) 15 | 16 | actual := strings.TrimSpace(testSCCStr(StronglyConnected(&g))) 17 | expected := strings.TrimSpace(testGraphStronglyConnectedStr) 18 | if actual != expected { 19 | t.Fatalf("bad: %s", actual) 20 | } 21 | } 22 | 23 | func TestGraphStronglyConnected_two(t *testing.T) { 24 | var g Graph 25 | g.Add(1) 26 | g.Add(2) 27 | g.Connect(BasicEdge(1, 2)) 28 | g.Connect(BasicEdge(2, 1)) 29 | g.Add(3) 30 | 31 | actual := strings.TrimSpace(testSCCStr(StronglyConnected(&g))) 32 | expected := strings.TrimSpace(testGraphStronglyConnectedTwoStr) 33 | if actual != expected { 34 | t.Fatalf("bad: %s", actual) 35 | } 36 | } 37 | 38 | func TestGraphStronglyConnected_three(t *testing.T) { 39 | var g Graph 40 | g.Add(1) 41 | g.Add(2) 42 | g.Connect(BasicEdge(1, 2)) 43 | g.Connect(BasicEdge(2, 1)) 44 | g.Add(3) 45 | g.Add(4) 46 | g.Add(5) 47 | g.Add(6) 48 | g.Connect(BasicEdge(4, 5)) 49 | g.Connect(BasicEdge(5, 6)) 50 | g.Connect(BasicEdge(6, 4)) 51 | 52 | actual := strings.TrimSpace(testSCCStr(StronglyConnected(&g))) 53 | expected := strings.TrimSpace(testGraphStronglyConnectedThreeStr) 54 | if actual != expected { 55 | t.Fatalf("bad: %s", actual) 56 | } 57 | } 58 | 59 | func testSCCStr(list [][]Vertex) string { 60 | var lines []string 61 | for _, vs := range list { 62 | result := make([]string, len(vs)) 63 | for i, v := range vs { 64 | result[i] = VertexName(v) 65 | } 66 | 67 | sort.Strings(result) 68 | lines = append(lines, strings.Join(result, ",")) 69 | } 70 | 71 | sort.Strings(lines) 72 | return strings.Join(lines, "\n") 73 | } 74 | 75 | const testGraphStronglyConnectedStr = `1,2` 76 | 77 | const testGraphStronglyConnectedTwoStr = ` 78 | 1,2 79 | 3 80 | ` 81 | 82 | const testGraphStronglyConnectedThreeStr = ` 83 | 1,2 84 | 3 85 | 4,5,6 86 | ` 87 | -------------------------------------------------------------------------------- /dag/marshal_test.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestGraphDot_empty(t *testing.T) { 9 | var g Graph 10 | g.Add(1) 11 | g.Add(2) 12 | g.Add(3) 13 | 14 | actual := strings.TrimSpace(string(g.Dot(nil))) 15 | expected := strings.TrimSpace(testGraphDotEmptyStr) 16 | if actual != expected { 17 | t.Fatalf("bad: %s", actual) 18 | } 19 | } 20 | 21 | func TestGraphDot_basic(t *testing.T) { 22 | var g Graph 23 | g.Add(1) 24 | g.Add(2) 25 | g.Add(3) 26 | g.Connect(BasicEdge(1, 3)) 27 | 28 | actual := strings.TrimSpace(string(g.Dot(nil))) 29 | expected := strings.TrimSpace(testGraphDotBasicStr) 30 | if actual != expected { 31 | t.Fatalf("bad: %s", actual) 32 | } 33 | } 34 | 35 | func TestGraphDot_quoted(t *testing.T) { 36 | var g Graph 37 | quoted := `name["with-quotes"]` 38 | other := `other` 39 | g.Add(quoted) 40 | g.Add(other) 41 | g.Connect(BasicEdge(quoted, other)) 42 | 43 | actual := strings.TrimSpace(string(g.Dot(nil))) 44 | expected := strings.TrimSpace(testGraphDotQuotedStr) 45 | if actual != expected { 46 | t.Fatalf("\ngot: %q\nwanted %q\n", actual, expected) 47 | } 48 | } 49 | 50 | func TestGraphDot_attrs(t *testing.T) { 51 | var g Graph 52 | g.Add(&testGraphNodeDotter{ 53 | Result: &DotNode{ 54 | Name: "foo", 55 | Attrs: map[string]string{"foo": "bar"}, 56 | }, 57 | }) 58 | 59 | actual := strings.TrimSpace(string(g.Dot(nil))) 60 | expected := strings.TrimSpace(testGraphDotAttrsStr) 61 | if actual != expected { 62 | t.Fatalf("bad: %s", actual) 63 | } 64 | } 65 | 66 | type testGraphNodeDotter struct{ Result *DotNode } 67 | 68 | func (n *testGraphNodeDotter) Name() string { return n.Result.Name } 69 | func (n *testGraphNodeDotter) DotNode(string, *DotOpts) *DotNode { return n.Result } 70 | 71 | const testGraphDotQuotedStr = `digraph { 72 | compound = "true" 73 | newrank = "true" 74 | subgraph "root" { 75 | "[root] name[\"with-quotes\"]" -> "[root] other" 76 | } 77 | }` 78 | 79 | const testGraphDotBasicStr = `digraph { 80 | compound = "true" 81 | newrank = "true" 82 | subgraph "root" { 83 | "[root] 1" -> "[root] 3" 84 | } 85 | } 86 | ` 87 | 88 | const testGraphDotEmptyStr = `digraph { 89 | compound = "true" 90 | newrank = "true" 91 | subgraph "root" { 92 | } 93 | }` 94 | 95 | const testGraphDotAttrsStr = `digraph { 96 | compound = "true" 97 | newrank = "true" 98 | subgraph "root" { 99 | "[root] foo" [foo = "bar"] 100 | } 101 | }` 102 | -------------------------------------------------------------------------------- /dag/set.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | // Set is a set data structure. 4 | type Set map[interface{}]interface{} 5 | 6 | // Hashable is the interface used by set to get the hash code of a value. 7 | // If this isn't given, then the value of the item being added to the set 8 | // itself is used as the comparison value. 9 | type Hashable interface { 10 | Hashcode() interface{} 11 | } 12 | 13 | // hashcode returns the hashcode used for set elements. 14 | func hashcode(v interface{}) interface{} { 15 | if h, ok := v.(Hashable); ok { 16 | return h.Hashcode() 17 | } 18 | 19 | return v 20 | } 21 | 22 | // Add adds an item to the set 23 | func (s Set) Add(v interface{}) { 24 | s[hashcode(v)] = v 25 | } 26 | 27 | // Delete removes an item from the set. 28 | func (s Set) Delete(v interface{}) { 29 | delete(s, hashcode(v)) 30 | } 31 | 32 | // Include returns true/false of whether a value is in the set. 33 | func (s Set) Include(v interface{}) bool { 34 | _, ok := s[hashcode(v)] 35 | return ok 36 | } 37 | 38 | // Intersection computes the set intersection with other. 39 | func (s Set) Intersection(other Set) Set { 40 | result := make(Set) 41 | if s == nil || other == nil { 42 | return result 43 | } 44 | // Iteration over a smaller set has better performance. 45 | if other.Len() < s.Len() { 46 | s, other = other, s 47 | } 48 | for _, v := range s { 49 | if other.Include(v) { 50 | result.Add(v) 51 | } 52 | } 53 | return result 54 | } 55 | 56 | // Difference returns a set with the elements that s has but 57 | // other doesn't. 58 | func (s Set) Difference(other Set) Set { 59 | if other == nil || other.Len() == 0 { 60 | return s.Copy() 61 | } 62 | 63 | result := make(Set) 64 | for k, v := range s { 65 | if _, ok := other[k]; !ok { 66 | result.Add(v) 67 | } 68 | } 69 | 70 | return result 71 | } 72 | 73 | // Filter returns a set that contains the elements from the receiver 74 | // where the given callback returns true. 75 | func (s Set) Filter(cb func(interface{}) bool) Set { 76 | result := make(Set) 77 | 78 | for _, v := range s { 79 | if cb(v) { 80 | result.Add(v) 81 | } 82 | } 83 | 84 | return result 85 | } 86 | 87 | // Len is the number of items in the set. 88 | func (s Set) Len() int { 89 | return len(s) 90 | } 91 | 92 | // List returns the list of set elements. 93 | func (s Set) List() []interface{} { 94 | if s == nil { 95 | return nil 96 | } 97 | 98 | r := make([]interface{}, 0, len(s)) 99 | for _, v := range s { 100 | r = append(r, v) 101 | } 102 | 103 | return r 104 | } 105 | 106 | // Copy returns a shallow copy of the set. 107 | func (s Set) Copy() Set { 108 | c := make(Set, len(s)) 109 | for k, v := range s { 110 | c[k] = v 111 | } 112 | return c 113 | } 114 | -------------------------------------------------------------------------------- /dag/tarjan.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | // StronglyConnected returns the list of strongly connected components 4 | // within the Graph g. This information is primarily used by this package 5 | // for cycle detection, but strongly connected components have widespread 6 | // use. 7 | func StronglyConnected(g *Graph) [][]Vertex { 8 | vs := g.Vertices() 9 | acct := sccAcct{ 10 | NextIndex: 1, 11 | VertexIndex: make(map[Vertex]int, len(vs)), 12 | } 13 | for _, v := range vs { 14 | // Recurse on any non-visited nodes 15 | if acct.VertexIndex[v] == 0 { 16 | stronglyConnected(&acct, g, v) 17 | } 18 | } 19 | return acct.SCC 20 | } 21 | 22 | func stronglyConnected(acct *sccAcct, g *Graph, v Vertex) int { 23 | // Initial vertex visit 24 | index := acct.visit(v) 25 | minIdx := index 26 | 27 | for _, raw := range g.downEdgesNoCopy(v) { 28 | target := raw.(Vertex) 29 | targetIdx := acct.VertexIndex[target] 30 | 31 | // Recurse on successor if not yet visited 32 | if targetIdx == 0 { 33 | minIdx = min(minIdx, stronglyConnected(acct, g, target)) 34 | } else if acct.inStack(target) { 35 | // Check if the vertex is in the stack 36 | minIdx = min(minIdx, targetIdx) 37 | } 38 | } 39 | 40 | // Pop the strongly connected components off the stack if 41 | // this is a root vertex 42 | if index == minIdx { 43 | var scc []Vertex 44 | for { 45 | v2 := acct.pop() 46 | scc = append(scc, v2) 47 | if v2 == v { 48 | break 49 | } 50 | } 51 | 52 | acct.SCC = append(acct.SCC, scc) 53 | } 54 | 55 | return minIdx 56 | } 57 | 58 | func min(a, b int) int { 59 | if a <= b { 60 | return a 61 | } 62 | return b 63 | } 64 | 65 | // sccAcct is used ot pass around accounting information for 66 | // the StronglyConnectedComponents algorithm 67 | type sccAcct struct { 68 | NextIndex int 69 | VertexIndex map[Vertex]int 70 | Stack []Vertex 71 | SCC [][]Vertex 72 | } 73 | 74 | // visit assigns an index and pushes a vertex onto the stack 75 | func (s *sccAcct) visit(v Vertex) int { 76 | idx := s.NextIndex 77 | s.VertexIndex[v] = idx 78 | s.NextIndex++ 79 | s.push(v) 80 | return idx 81 | } 82 | 83 | // push adds a vertex to the stack 84 | func (s *sccAcct) push(n Vertex) { 85 | s.Stack = append(s.Stack, n) 86 | } 87 | 88 | // pop removes a vertex from the stack 89 | func (s *sccAcct) pop() Vertex { 90 | n := len(s.Stack) 91 | if n == 0 { 92 | return nil 93 | } 94 | vertex := s.Stack[n-1] 95 | s.Stack = s.Stack[:n-1] 96 | return vertex 97 | } 98 | 99 | // inStack checks if a vertex is in the stack 100 | func (s *sccAcct) inStack(needle Vertex) bool { 101 | for _, n := range s.Stack { 102 | if n == needle { 103 | return true 104 | } 105 | } 106 | return false 107 | } 108 | -------------------------------------------------------------------------------- /dag/set_test.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestSetDifference(t *testing.T) { 9 | cases := []struct { 10 | Name string 11 | A, B []interface{} 12 | Expected []interface{} 13 | }{ 14 | { 15 | "same", 16 | []interface{}{1, 2, 3}, 17 | []interface{}{3, 1, 2}, 18 | []interface{}{}, 19 | }, 20 | 21 | { 22 | "A has extra elements", 23 | []interface{}{1, 2, 3}, 24 | []interface{}{3, 2}, 25 | []interface{}{1}, 26 | }, 27 | 28 | { 29 | "B has extra elements", 30 | []interface{}{1, 2, 3}, 31 | []interface{}{3, 2, 1, 4}, 32 | []interface{}{}, 33 | }, 34 | { 35 | "B is nil", 36 | []interface{}{1, 2, 3}, 37 | nil, 38 | []interface{}{1, 2, 3}, 39 | }, 40 | } 41 | 42 | for i, tc := range cases { 43 | t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 44 | one := make(Set) 45 | two := make(Set) 46 | expected := make(Set) 47 | for _, v := range tc.A { 48 | one.Add(v) 49 | } 50 | for _, v := range tc.B { 51 | two.Add(v) 52 | } 53 | if tc.B == nil { 54 | two = nil 55 | } 56 | for _, v := range tc.Expected { 57 | expected.Add(v) 58 | } 59 | 60 | actual := one.Difference(two) 61 | match := actual.Intersection(expected) 62 | if match.Len() != expected.Len() { 63 | t.Fatalf("bad: %#v", actual.List()) 64 | } 65 | }) 66 | } 67 | } 68 | 69 | func TestSetFilter(t *testing.T) { 70 | cases := []struct { 71 | Input []interface{} 72 | Expected []interface{} 73 | }{ 74 | { 75 | []interface{}{1, 2, 3}, 76 | []interface{}{1, 2, 3}, 77 | }, 78 | 79 | { 80 | []interface{}{4, 5, 6}, 81 | []interface{}{4}, 82 | }, 83 | 84 | { 85 | []interface{}{7, 8, 9}, 86 | []interface{}{}, 87 | }, 88 | } 89 | 90 | for i, tc := range cases { 91 | t.Run(fmt.Sprintf("%d-%#v", i, tc.Input), func(t *testing.T) { 92 | input := make(Set) 93 | expected := make(Set) 94 | for _, v := range tc.Input { 95 | input.Add(v) 96 | } 97 | for _, v := range tc.Expected { 98 | expected.Add(v) 99 | } 100 | 101 | actual := input.Filter(func(v interface{}) bool { 102 | return v.(int) < 5 103 | }) 104 | match := actual.Intersection(expected) 105 | if match.Len() != expected.Len() { 106 | t.Fatalf("bad: %#v", actual.List()) 107 | } 108 | }) 109 | } 110 | } 111 | 112 | func TestSetCopy(t *testing.T) { 113 | a := make(Set) 114 | a.Add(1) 115 | a.Add(2) 116 | 117 | b := a.Copy() 118 | b.Add(3) 119 | 120 | diff := b.Difference(a) 121 | 122 | if diff.Len() != 1 { 123 | t.Fatalf("expected single diff value, got %#v", diff) 124 | } 125 | 126 | if !diff.Include(3) { 127 | t.Fatalf("diff does not contain 3, got %#v", diff) 128 | } 129 | 130 | } 131 | 132 | func makeSet(n int) Set { 133 | ret := make(Set, n) 134 | for i := 0; i < n; i++ { 135 | ret.Add(i) 136 | } 137 | return ret 138 | } 139 | 140 | func BenchmarkSetIntersection_100_100000(b *testing.B) { 141 | small := makeSet(100) 142 | large := makeSet(100000) 143 | 144 | b.ResetTimer() 145 | for n := 0; n < b.N; n++ { 146 | small.Intersection(large) 147 | } 148 | } 149 | 150 | func BenchmarkSetIntersection_100000_100(b *testing.B) { 151 | small := makeSet(100) 152 | large := makeSet(100000) 153 | 154 | b.ResetTimer() 155 | for n := 0; n < b.N; n++ { 156 | large.Intersection(small) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /dag/graph_test.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestGraph_empty(t *testing.T) { 10 | var g Graph 11 | g.Add(1) 12 | g.Add(2) 13 | g.Add(3) 14 | 15 | actual := strings.TrimSpace(g.String()) 16 | expected := strings.TrimSpace(testGraphEmptyStr) 17 | if actual != expected { 18 | t.Fatalf("bad: %s", actual) 19 | } 20 | } 21 | 22 | func TestGraph_basic(t *testing.T) { 23 | var g Graph 24 | g.Add(1) 25 | g.Add(2) 26 | g.Add(3) 27 | g.Connect(BasicEdge(1, 3)) 28 | 29 | actual := strings.TrimSpace(g.String()) 30 | expected := strings.TrimSpace(testGraphBasicStr) 31 | if actual != expected { 32 | t.Fatalf("bad: %s", actual) 33 | } 34 | } 35 | 36 | func TestGraph_remove(t *testing.T) { 37 | var g Graph 38 | g.Add(1) 39 | g.Add(2) 40 | g.Add(3) 41 | g.Connect(BasicEdge(1, 3)) 42 | g.Remove(3) 43 | 44 | actual := strings.TrimSpace(g.String()) 45 | expected := strings.TrimSpace(testGraphRemoveStr) 46 | if actual != expected { 47 | t.Fatalf("bad: %s", actual) 48 | } 49 | } 50 | 51 | func TestGraph_replace(t *testing.T) { 52 | var g Graph 53 | g.Add(1) 54 | g.Add(2) 55 | g.Add(3) 56 | g.Connect(BasicEdge(1, 2)) 57 | g.Connect(BasicEdge(2, 3)) 58 | g.Replace(2, 42) 59 | 60 | actual := strings.TrimSpace(g.String()) 61 | expected := strings.TrimSpace(testGraphReplaceStr) 62 | if actual != expected { 63 | t.Fatalf("bad: %s", actual) 64 | } 65 | } 66 | 67 | func TestGraph_replaceSelf(t *testing.T) { 68 | var g Graph 69 | g.Add(1) 70 | g.Add(2) 71 | g.Add(3) 72 | g.Connect(BasicEdge(1, 2)) 73 | g.Connect(BasicEdge(2, 3)) 74 | g.Replace(2, 2) 75 | 76 | actual := strings.TrimSpace(g.String()) 77 | expected := strings.TrimSpace(testGraphReplaceSelfStr) 78 | if actual != expected { 79 | t.Fatalf("bad: %s", actual) 80 | } 81 | } 82 | 83 | // This tests that connecting edges works based on custom Hashcode 84 | // implementations for uniqueness. 85 | func TestGraph_hashcode(t *testing.T) { 86 | var g Graph 87 | g.Add(&hashVertex{code: 1}) 88 | g.Add(&hashVertex{code: 2}) 89 | g.Add(&hashVertex{code: 3}) 90 | g.Connect(BasicEdge( 91 | &hashVertex{code: 1}, 92 | &hashVertex{code: 3})) 93 | 94 | actual := strings.TrimSpace(g.String()) 95 | expected := strings.TrimSpace(testGraphBasicStr) 96 | if actual != expected { 97 | t.Fatalf("bad: %s", actual) 98 | } 99 | } 100 | 101 | func TestGraphHasVertex(t *testing.T) { 102 | var g Graph 103 | g.Add(1) 104 | 105 | if !g.HasVertex(1) { 106 | t.Fatal("should have 1") 107 | } 108 | if g.HasVertex(2) { 109 | t.Fatal("should not have 2") 110 | } 111 | } 112 | 113 | func TestGraphHasEdge(t *testing.T) { 114 | var g Graph 115 | g.Add(1) 116 | g.Add(2) 117 | g.Connect(BasicEdge(1, 2)) 118 | 119 | if !g.HasEdge(BasicEdge(1, 2)) { 120 | t.Fatal("should have 1,2") 121 | } 122 | if g.HasVertex(BasicEdge(2, 3)) { 123 | t.Fatal("should not have 2,3") 124 | } 125 | } 126 | 127 | func TestGraphEdgesFrom(t *testing.T) { 128 | var g Graph 129 | g.Add(1) 130 | g.Add(2) 131 | g.Add(3) 132 | g.Connect(BasicEdge(1, 3)) 133 | g.Connect(BasicEdge(2, 3)) 134 | 135 | edges := g.EdgesFrom(1) 136 | 137 | expected := make(Set) 138 | expected.Add(BasicEdge(1, 3)) 139 | 140 | s := make(Set) 141 | for _, e := range edges { 142 | s.Add(e) 143 | } 144 | 145 | if s.Intersection(expected).Len() != expected.Len() { 146 | t.Fatalf("bad: %#v", edges) 147 | } 148 | } 149 | 150 | func TestGraphEdgesTo(t *testing.T) { 151 | var g Graph 152 | g.Add(1) 153 | g.Add(2) 154 | g.Add(3) 155 | g.Connect(BasicEdge(1, 3)) 156 | g.Connect(BasicEdge(1, 2)) 157 | 158 | edges := g.EdgesTo(3) 159 | 160 | expected := make(Set) 161 | expected.Add(BasicEdge(1, 3)) 162 | 163 | s := make(Set) 164 | for _, e := range edges { 165 | s.Add(e) 166 | } 167 | 168 | if s.Intersection(expected).Len() != expected.Len() { 169 | t.Fatalf("bad: %#v", edges) 170 | } 171 | } 172 | 173 | func TestGraphUpdownEdges(t *testing.T) { 174 | // Verify that we can't inadvertently modify the internal graph sets 175 | var g Graph 176 | g.Add(1) 177 | g.Add(2) 178 | g.Add(3) 179 | g.Connect(BasicEdge(1, 2)) 180 | g.Connect(BasicEdge(2, 3)) 181 | 182 | up := g.UpEdges(2) 183 | if up.Len() != 1 || !up.Include(1) { 184 | t.Fatalf("expected only an up edge of '1', got %#v", up) 185 | } 186 | // modify the up set 187 | up.Add(9) 188 | 189 | orig := g.UpEdges(2) 190 | diff := up.Difference(orig) 191 | if diff.Len() != 1 || !diff.Include(9) { 192 | t.Fatalf("expected a diff of only '9', got %#v", diff) 193 | } 194 | 195 | down := g.DownEdges(2) 196 | if down.Len() != 1 || !down.Include(3) { 197 | t.Fatalf("expected only a down edge of '3', got %#v", down) 198 | } 199 | // modify the down set 200 | down.Add(8) 201 | 202 | orig = g.DownEdges(2) 203 | diff = down.Difference(orig) 204 | if diff.Len() != 1 || !diff.Include(8) { 205 | t.Fatalf("expected a diff of only '8', got %#v", diff) 206 | } 207 | } 208 | 209 | type hashVertex struct { 210 | code interface{} 211 | } 212 | 213 | func (v *hashVertex) Hashcode() interface{} { 214 | return v.code 215 | } 216 | 217 | func (v *hashVertex) Name() string { 218 | return fmt.Sprintf("%#v", v.code) 219 | } 220 | 221 | const testGraphBasicStr = ` 222 | 1 223 | 3 224 | 2 225 | 3 226 | ` 227 | 228 | const testGraphEmptyStr = ` 229 | 1 230 | 2 231 | 3 232 | ` 233 | 234 | const testGraphRemoveStr = ` 235 | 1 236 | 2 237 | ` 238 | 239 | const testGraphReplaceStr = ` 240 | 1 241 | 42 242 | 3 243 | 42 244 | 3 245 | ` 246 | 247 | const testGraphReplaceSelfStr = ` 248 | 1 249 | 2 250 | 2 251 | 3 252 | 3 253 | ` 254 | -------------------------------------------------------------------------------- /dag/marshal.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sort" 7 | "strconv" 8 | ) 9 | 10 | // the marshal* structs are for serialization of the graph data. 11 | type marshalGraph struct { 12 | // Type is always "Graph", for identification as a top level object in the 13 | // JSON stream. 14 | Type string 15 | 16 | // Each marshal structure requires a unique ID so that it can be referenced 17 | // by other structures. 18 | ID string `json:",omitempty"` 19 | 20 | // Human readable name for this graph. 21 | Name string `json:",omitempty"` 22 | 23 | // Arbitrary attributes that can be added to the output. 24 | Attrs map[string]string `json:",omitempty"` 25 | 26 | // List of graph vertices, sorted by ID. 27 | Vertices []*marshalVertex `json:",omitempty"` 28 | 29 | // List of edges, sorted by Source ID. 30 | Edges []*marshalEdge `json:",omitempty"` 31 | 32 | // Any number of subgraphs. A subgraph itself is considered a vertex, and 33 | // may be referenced by either end of an edge. 34 | Subgraphs []*marshalGraph `json:",omitempty"` 35 | 36 | // Any lists of vertices that are included in cycles. 37 | Cycles [][]*marshalVertex `json:",omitempty"` 38 | } 39 | 40 | func (g *marshalGraph) vertexByID(id string) *marshalVertex { 41 | for _, v := range g.Vertices { 42 | if id == v.ID { 43 | return v 44 | } 45 | } 46 | return nil 47 | } 48 | 49 | type marshalVertex struct { 50 | // Unique ID, used to reference this vertex from other structures. 51 | ID string 52 | 53 | // Human readable name 54 | Name string `json:",omitempty"` 55 | 56 | Attrs map[string]string `json:",omitempty"` 57 | 58 | // This is to help transition from the old Dot interfaces. We record if the 59 | // node was a GraphNodeDotter here, so we can call it to get attributes. 60 | graphNodeDotter GraphNodeDotter 61 | } 62 | 63 | func newMarshalVertex(v Vertex) *marshalVertex { 64 | dn, ok := v.(GraphNodeDotter) 65 | if !ok { 66 | dn = nil 67 | } 68 | 69 | // the name will be quoted again later, so we need to ensure it's properly 70 | // escaped without quotes. 71 | name := strconv.Quote(VertexName(v)) 72 | name = name[1 : len(name)-1] 73 | 74 | return &marshalVertex{ 75 | ID: marshalVertexID(v), 76 | Name: name, 77 | Attrs: make(map[string]string), 78 | graphNodeDotter: dn, 79 | } 80 | } 81 | 82 | // vertices is a sort.Interface implementation for sorting vertices by ID 83 | type vertices []*marshalVertex 84 | 85 | func (v vertices) Less(i, j int) bool { return v[i].Name < v[j].Name } 86 | func (v vertices) Len() int { return len(v) } 87 | func (v vertices) Swap(i, j int) { v[i], v[j] = v[j], v[i] } 88 | 89 | type marshalEdge struct { 90 | // Human readable name 91 | Name string 92 | 93 | // Source and Target Vertices by ID 94 | Source string 95 | Target string 96 | 97 | Attrs map[string]string `json:",omitempty"` 98 | } 99 | 100 | func newMarshalEdge(e Edge) *marshalEdge { 101 | return &marshalEdge{ 102 | Name: fmt.Sprintf("%s|%s", VertexName(e.Source()), VertexName(e.Target())), 103 | Source: marshalVertexID(e.Source()), 104 | Target: marshalVertexID(e.Target()), 105 | Attrs: make(map[string]string), 106 | } 107 | } 108 | 109 | // edges is a sort.Interface implementation for sorting edges by Source ID 110 | type edges []*marshalEdge 111 | 112 | func (e edges) Less(i, j int) bool { return e[i].Name < e[j].Name } 113 | func (e edges) Len() int { return len(e) } 114 | func (e edges) Swap(i, j int) { e[i], e[j] = e[j], e[i] } 115 | 116 | // build a marshalGraph structure from a *Graph 117 | func newMarshalGraph(name string, g *Graph) *marshalGraph { 118 | mg := &marshalGraph{ 119 | Type: "Graph", 120 | Name: name, 121 | Attrs: make(map[string]string), 122 | } 123 | 124 | for _, v := range g.Vertices() { 125 | id := marshalVertexID(v) 126 | if sg, ok := marshalSubgrapher(v); ok { 127 | smg := newMarshalGraph(VertexName(v), sg) 128 | smg.ID = id 129 | mg.Subgraphs = append(mg.Subgraphs, smg) 130 | } 131 | 132 | mv := newMarshalVertex(v) 133 | mg.Vertices = append(mg.Vertices, mv) 134 | } 135 | 136 | sort.Sort(vertices(mg.Vertices)) 137 | 138 | for _, e := range g.Edges() { 139 | mg.Edges = append(mg.Edges, newMarshalEdge(e)) 140 | } 141 | 142 | sort.Sort(edges(mg.Edges)) 143 | 144 | for _, c := range (&AcyclicGraph{*g}).Cycles() { 145 | var cycle []*marshalVertex 146 | for _, v := range c { 147 | mv := newMarshalVertex(v) 148 | cycle = append(cycle, mv) 149 | } 150 | mg.Cycles = append(mg.Cycles, cycle) 151 | } 152 | 153 | return mg 154 | } 155 | 156 | // Attempt to return a unique ID for any vertex. 157 | func marshalVertexID(v Vertex) string { 158 | val := reflect.ValueOf(v) 159 | switch val.Kind() { 160 | case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: 161 | return strconv.Itoa(int(val.Pointer())) 162 | case reflect.Interface: 163 | // A vertex shouldn't contain another layer of interface, but handle 164 | // this just in case. 165 | return fmt.Sprintf("%#v", val.Interface()) 166 | } 167 | 168 | if v, ok := v.(Hashable); ok { 169 | h := v.Hashcode() 170 | if h, ok := h.(string); ok { 171 | return h 172 | } 173 | } 174 | 175 | // fallback to a name, which we hope is unique. 176 | return VertexName(v) 177 | 178 | // we could try harder by attempting to read the arbitrary value from the 179 | // interface, but we shouldn't get here from terraform right now. 180 | } 181 | 182 | // check for a Subgrapher, and return the underlying *Graph. 183 | func marshalSubgrapher(v Vertex) (*Graph, bool) { 184 | sg, ok := v.(Subgrapher) 185 | if !ok { 186 | return nil, false 187 | } 188 | 189 | switch g := sg.Subgraph().DirectedGraph().(type) { 190 | case *Graph: 191 | return g, true 192 | case *AcyclicGraph: 193 | return &g.Graph, true 194 | } 195 | 196 | return nil, false 197 | } 198 | -------------------------------------------------------------------------------- /dag/walk_test.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sync" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestWalker_basic(t *testing.T) { 12 | var g AcyclicGraph 13 | g.Add(1) 14 | g.Add(2) 15 | g.Connect(BasicEdge(1, 2)) 16 | 17 | // Run it a bunch of times since it is timing dependent 18 | for i := 0; i < 50; i++ { 19 | var order []interface{} 20 | w := &Walker{Callback: walkCbRecord(&order)} 21 | w.Update(&g) 22 | 23 | // Wait 24 | if err := w.Wait(); err != nil { 25 | t.Fatalf("err: %s", err) 26 | } 27 | 28 | // Check 29 | expected := []interface{}{1, 2} 30 | if !reflect.DeepEqual(order, expected) { 31 | t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected) 32 | } 33 | } 34 | } 35 | 36 | func TestWalker_updateNilGraph(t *testing.T) { 37 | var g AcyclicGraph 38 | g.Add(1) 39 | g.Add(2) 40 | g.Connect(BasicEdge(1, 2)) 41 | 42 | // Run it a bunch of times since it is timing dependent 43 | for i := 0; i < 50; i++ { 44 | var order []interface{} 45 | w := &Walker{Callback: walkCbRecord(&order)} 46 | w.Update(&g) 47 | w.Update(nil) 48 | 49 | // Wait 50 | if err := w.Wait(); err != nil { 51 | t.Fatalf("err: %s", err) 52 | } 53 | } 54 | } 55 | 56 | func TestWalker_error(t *testing.T) { 57 | var g AcyclicGraph 58 | g.Add(1) 59 | g.Add(2) 60 | g.Add(3) 61 | g.Add(4) 62 | g.Connect(BasicEdge(1, 2)) 63 | g.Connect(BasicEdge(2, 3)) 64 | g.Connect(BasicEdge(3, 4)) 65 | 66 | // Record function 67 | var order []interface{} 68 | recordF := walkCbRecord(&order) 69 | 70 | // Build a callback that delays until we close a channel 71 | cb := func(v Vertex) error { 72 | if v == 2 { 73 | return fmt.Errorf("error") 74 | } 75 | 76 | return recordF(v) 77 | } 78 | 79 | w := &Walker{Callback: cb} 80 | w.Update(&g) 81 | 82 | // Wait 83 | if err := w.Wait(); err == nil { 84 | t.Fatal("expect error") 85 | } 86 | 87 | // Check 88 | expected := []interface{}{1} 89 | if !reflect.DeepEqual(order, expected) { 90 | t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected) 91 | } 92 | } 93 | 94 | func TestWalker_newVertex(t *testing.T) { 95 | var g AcyclicGraph 96 | g.Add(1) 97 | g.Add(2) 98 | g.Connect(BasicEdge(1, 2)) 99 | 100 | // Record function 101 | var order []interface{} 102 | recordF := walkCbRecord(&order) 103 | done2 := make(chan int) 104 | 105 | // Build a callback that notifies us when 2 has been walked 106 | var w *Walker 107 | cb := func(v Vertex) error { 108 | if v == 2 { 109 | defer close(done2) 110 | } 111 | return recordF(v) 112 | } 113 | 114 | // Add the initial vertices 115 | w = &Walker{Callback: cb} 116 | w.Update(&g) 117 | 118 | // if 2 has been visited, the walk is complete so far 119 | <-done2 120 | 121 | // Update the graph 122 | g.Add(3) 123 | w.Update(&g) 124 | 125 | // Update the graph again but with the same vertex 126 | g.Add(3) 127 | w.Update(&g) 128 | 129 | // Wait 130 | if err := w.Wait(); err != nil { 131 | t.Fatalf("err: %s", err) 132 | } 133 | 134 | // Check 135 | expected := []interface{}{1, 2, 3} 136 | if !reflect.DeepEqual(order, expected) { 137 | t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected) 138 | } 139 | } 140 | 141 | func TestWalker_removeVertex(t *testing.T) { 142 | var g AcyclicGraph 143 | g.Add(1) 144 | g.Add(2) 145 | g.Connect(BasicEdge(1, 2)) 146 | 147 | // Record function 148 | var order []interface{} 149 | recordF := walkCbRecord(&order) 150 | 151 | var w *Walker 152 | cb := func(v Vertex) error { 153 | if v == 1 { 154 | g.Remove(2) 155 | w.Update(&g) 156 | } 157 | 158 | return recordF(v) 159 | } 160 | 161 | // Add the initial vertices 162 | w = &Walker{Callback: cb} 163 | w.Update(&g) 164 | 165 | // Wait 166 | if err := w.Wait(); err != nil { 167 | t.Fatalf("err: %s", err) 168 | } 169 | 170 | // Check 171 | expected := []interface{}{1} 172 | if !reflect.DeepEqual(order, expected) { 173 | t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected) 174 | } 175 | } 176 | 177 | func TestWalker_newEdge(t *testing.T) { 178 | var g AcyclicGraph 179 | g.Add(1) 180 | g.Add(2) 181 | g.Connect(BasicEdge(1, 2)) 182 | 183 | // Record function 184 | var order []interface{} 185 | recordF := walkCbRecord(&order) 186 | 187 | var w *Walker 188 | cb := func(v Vertex) error { 189 | // record where we are first, otherwise the Updated vertex may get 190 | // walked before the first visit. 191 | diags := recordF(v) 192 | 193 | if v == 1 { 194 | g.Add(3) 195 | g.Connect(BasicEdge(3, 2)) 196 | w.Update(&g) 197 | } 198 | return diags 199 | } 200 | 201 | // Add the initial vertices 202 | w = &Walker{Callback: cb} 203 | w.Update(&g) 204 | 205 | // Wait 206 | if err := w.Wait(); err != nil { 207 | t.Fatalf("err: %s", err) 208 | } 209 | 210 | // Check 211 | expected := []interface{}{1, 3, 2} 212 | if !reflect.DeepEqual(order, expected) { 213 | t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected) 214 | } 215 | } 216 | 217 | func TestWalker_removeEdge(t *testing.T) { 218 | var g AcyclicGraph 219 | g.Add(1) 220 | g.Add(2) 221 | g.Add(3) 222 | g.Connect(BasicEdge(1, 2)) 223 | g.Connect(BasicEdge(1, 3)) 224 | g.Connect(BasicEdge(3, 2)) 225 | 226 | // Record function 227 | var order []interface{} 228 | recordF := walkCbRecord(&order) 229 | 230 | // The way this works is that our original graph forces 231 | // the order of 1 => 3 => 2. During the execution of 1, we 232 | // remove the edge forcing 3 before 2. Then, during the execution 233 | // of 3, we wait on a channel that is only closed by 2, implicitly 234 | // forcing 2 before 3 via the callback (and not the graph). If 235 | // 2 cannot execute before 3 (edge removal is non-functional), then 236 | // this test will timeout. 237 | var w *Walker 238 | gateCh := make(chan struct{}) 239 | cb := func(v Vertex) error { 240 | t.Logf("visit vertex %#v", v) 241 | switch v { 242 | case 1: 243 | g.RemoveEdge(BasicEdge(3, 2)) 244 | w.Update(&g) 245 | t.Logf("removed edge from 3 to 2") 246 | 247 | case 2: 248 | // this visit isn't completed until we've recorded it 249 | // Once the visit is official, we can then close the gate to 250 | // let 3 continue. 251 | defer close(gateCh) 252 | defer t.Logf("2 unblocked 3") 253 | 254 | case 3: 255 | select { 256 | case <-gateCh: 257 | t.Logf("vertex 3 gate channel is now closed") 258 | case <-time.After(500 * time.Millisecond): 259 | t.Logf("vertex 3 timed out waiting for the gate channel to close") 260 | return fmt.Errorf("timeout 3 waiting for 2") 261 | } 262 | } 263 | 264 | return recordF(v) 265 | } 266 | 267 | // Add the initial vertices 268 | w = &Walker{Callback: cb} 269 | w.Update(&g) 270 | 271 | // Wait 272 | if diags := w.Wait(); diags != nil { 273 | t.Fatalf("unexpected errors: %s", diags.Error()) 274 | } 275 | 276 | // Check 277 | expected := []interface{}{1, 2, 3} 278 | if !reflect.DeepEqual(order, expected) { 279 | t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected) 280 | } 281 | } 282 | 283 | // walkCbRecord is a test helper callback that just records the order called. 284 | func walkCbRecord(order *[]interface{}) WalkFunc { 285 | var l sync.Mutex 286 | return func(v Vertex) error { 287 | l.Lock() 288 | defer l.Unlock() 289 | *order = append(*order, v) 290 | return nil 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /dag/dot.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sort" 7 | "strings" 8 | ) 9 | 10 | // DotOpts are the options for generating a dot formatted Graph. 11 | type DotOpts struct { 12 | // Allows some nodes to decide to only show themselves when the user has 13 | // requested the "verbose" graph. 14 | Verbose bool 15 | 16 | // Highlight Cycles 17 | DrawCycles bool 18 | 19 | // How many levels to expand modules as we draw 20 | MaxDepth int 21 | 22 | // use this to keep the cluster_ naming convention from the previous dot writer 23 | cluster bool 24 | } 25 | 26 | // GraphNodeDotter can be implemented by a node to cause it to be included 27 | // in the dot graph. The Dot method will be called which is expected to 28 | // return a representation of this node. 29 | type GraphNodeDotter interface { 30 | // Dot is called to return the dot formatting for the node. 31 | // The first parameter is the title of the node. 32 | // The second parameter includes user-specified options that affect the dot 33 | // graph. See GraphDotOpts below for details. 34 | DotNode(string, *DotOpts) *DotNode 35 | } 36 | 37 | // DotNode provides a structure for Vertices to return in order to specify their 38 | // dot format. 39 | type DotNode struct { 40 | Name string 41 | Attrs map[string]string 42 | } 43 | 44 | // Returns the DOT representation of this Graph. 45 | func (g *marshalGraph) Dot(opts *DotOpts) []byte { 46 | if opts == nil { 47 | opts = &DotOpts{ 48 | DrawCycles: true, 49 | MaxDepth: -1, 50 | Verbose: true, 51 | } 52 | } 53 | 54 | var w indentWriter 55 | w.WriteString("digraph {\n") 56 | w.Indent() 57 | 58 | // some dot defaults 59 | w.WriteString(`compound = "true"` + "\n") 60 | w.WriteString(`newrank = "true"` + "\n") 61 | 62 | // the top level graph is written as the first subgraph 63 | w.WriteString(`subgraph "root" {` + "\n") 64 | g.writeBody(opts, &w) 65 | 66 | // cluster isn't really used other than for naming purposes in some graphs 67 | opts.cluster = opts.MaxDepth != 0 68 | maxDepth := opts.MaxDepth 69 | if maxDepth == 0 { 70 | maxDepth = -1 71 | } 72 | 73 | for _, s := range g.Subgraphs { 74 | g.writeSubgraph(s, opts, maxDepth, &w) 75 | } 76 | 77 | w.Unindent() 78 | w.WriteString("}\n") 79 | return w.Bytes() 80 | } 81 | 82 | func (v *marshalVertex) dot(g *marshalGraph, opts *DotOpts) []byte { 83 | var buf bytes.Buffer 84 | graphName := g.Name 85 | if graphName == "" { 86 | graphName = "root" 87 | } 88 | 89 | name := v.Name 90 | attrs := v.Attrs 91 | if v.graphNodeDotter != nil { 92 | node := v.graphNodeDotter.DotNode(name, opts) 93 | if node == nil { 94 | return []byte{} 95 | } 96 | 97 | newAttrs := make(map[string]string) 98 | for k, v := range attrs { 99 | newAttrs[k] = v 100 | } 101 | for k, v := range node.Attrs { 102 | newAttrs[k] = v 103 | } 104 | 105 | name = node.Name 106 | attrs = newAttrs 107 | } 108 | 109 | buf.WriteString(fmt.Sprintf(`"[%s] %s"`, graphName, name)) 110 | writeAttrs(&buf, attrs) 111 | buf.WriteByte('\n') 112 | 113 | return buf.Bytes() 114 | } 115 | 116 | func (e *marshalEdge) dot(g *marshalGraph) string { 117 | var buf bytes.Buffer 118 | graphName := g.Name 119 | if graphName == "" { 120 | graphName = "root" 121 | } 122 | 123 | sourceName := g.vertexByID(e.Source).Name 124 | targetName := g.vertexByID(e.Target).Name 125 | s := fmt.Sprintf(`"[%s] %s" -> "[%s] %s"`, graphName, sourceName, graphName, targetName) 126 | buf.WriteString(s) 127 | writeAttrs(&buf, e.Attrs) 128 | 129 | return buf.String() 130 | } 131 | 132 | func cycleDot(e *marshalEdge, g *marshalGraph) string { 133 | return e.dot(g) + ` [color = "red", penwidth = "2.0"]` 134 | } 135 | 136 | // Write the subgraph body. The is recursive, and the depth argument is used to 137 | // record the current depth of iteration. 138 | func (g *marshalGraph) writeSubgraph(sg *marshalGraph, opts *DotOpts, depth int, w *indentWriter) { 139 | if depth == 0 { 140 | return 141 | } 142 | depth-- 143 | 144 | name := sg.Name 145 | if opts.cluster { 146 | // we prefix with cluster_ to match the old dot output 147 | name = "cluster_" + name 148 | sg.Attrs["label"] = sg.Name 149 | } 150 | w.WriteString(fmt.Sprintf("subgraph %q {\n", name)) 151 | sg.writeBody(opts, w) 152 | 153 | for _, sg := range sg.Subgraphs { 154 | g.writeSubgraph(sg, opts, depth, w) 155 | } 156 | } 157 | 158 | func (g *marshalGraph) writeBody(opts *DotOpts, w *indentWriter) { 159 | w.Indent() 160 | 161 | for _, as := range attrStrings(g.Attrs) { 162 | w.WriteString(as + "\n") 163 | } 164 | 165 | // list of Vertices that aren't to be included in the dot output 166 | skip := map[string]bool{} 167 | 168 | for _, v := range g.Vertices { 169 | if v.graphNodeDotter == nil { 170 | skip[v.ID] = true 171 | continue 172 | } 173 | 174 | w.Write(v.dot(g, opts)) 175 | } 176 | 177 | var dotEdges []string 178 | 179 | if opts.DrawCycles { 180 | for _, c := range g.Cycles { 181 | if len(c) < 2 { 182 | continue 183 | } 184 | 185 | for i, j := 0, 1; i < len(c); i, j = i+1, j+1 { 186 | if j >= len(c) { 187 | j = 0 188 | } 189 | src := c[i] 190 | tgt := c[j] 191 | 192 | if skip[src.ID] || skip[tgt.ID] { 193 | continue 194 | } 195 | 196 | e := &marshalEdge{ 197 | Name: fmt.Sprintf("%s|%s", src.Name, tgt.Name), 198 | Source: src.ID, 199 | Target: tgt.ID, 200 | Attrs: make(map[string]string), 201 | } 202 | 203 | dotEdges = append(dotEdges, cycleDot(e, g)) 204 | src = tgt 205 | } 206 | } 207 | } 208 | 209 | for _, e := range g.Edges { 210 | dotEdges = append(dotEdges, e.dot(g)) 211 | } 212 | 213 | // srot these again to match the old output 214 | sort.Strings(dotEdges) 215 | 216 | for _, e := range dotEdges { 217 | w.WriteString(e + "\n") 218 | } 219 | 220 | w.Unindent() 221 | w.WriteString("}\n") 222 | } 223 | 224 | func writeAttrs(buf *bytes.Buffer, attrs map[string]string) { 225 | if len(attrs) > 0 { 226 | buf.WriteString(" [") 227 | buf.WriteString(strings.Join(attrStrings(attrs), ", ")) 228 | buf.WriteString("]") 229 | } 230 | } 231 | 232 | func attrStrings(attrs map[string]string) []string { 233 | strings := make([]string, 0, len(attrs)) 234 | for k, v := range attrs { 235 | strings = append(strings, fmt.Sprintf("%s = %q", k, v)) 236 | } 237 | sort.Strings(strings) 238 | return strings 239 | } 240 | 241 | // Provide a bytes.Buffer like structure, which will indent when starting a 242 | // newline. 243 | type indentWriter struct { 244 | bytes.Buffer 245 | level int 246 | } 247 | 248 | func (w *indentWriter) indent() { 249 | newline := []byte("\n") 250 | if !bytes.HasSuffix(w.Bytes(), newline) { 251 | return 252 | } 253 | for i := 0; i < w.level; i++ { 254 | w.Buffer.WriteString("\t") 255 | } 256 | } 257 | 258 | // Indent increases indentation by 1 259 | func (w *indentWriter) Indent() { w.level++ } 260 | 261 | // Unindent decreases indentation by 1 262 | func (w *indentWriter) Unindent() { w.level-- } 263 | 264 | // the following methods intercecpt the byte.Buffer writes and insert the 265 | // indentation when starting a new line. 266 | func (w *indentWriter) Write(b []byte) (int, error) { 267 | w.indent() 268 | return w.Buffer.Write(b) 269 | } 270 | 271 | func (w *indentWriter) WriteString(s string) (int, error) { 272 | w.indent() 273 | return w.Buffer.WriteString(s) 274 | } 275 | func (w *indentWriter) WriteByte(b byte) error { 276 | w.indent() 277 | return w.Buffer.WriteByte(b) 278 | } 279 | func (w *indentWriter) WriteRune(r rune) (int, error) { 280 | w.indent() 281 | return w.Buffer.WriteRune(r) 282 | } 283 | -------------------------------------------------------------------------------- /dag/graph.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sort" 7 | ) 8 | 9 | // Graph is used to represent a dependency graph. 10 | type Graph struct { 11 | vertices Set 12 | edges Set 13 | downEdges map[interface{}]Set 14 | upEdges map[interface{}]Set 15 | } 16 | 17 | // Subgrapher allows a Vertex to be a Graph itself, by returning a Grapher. 18 | type Subgrapher interface { 19 | Subgraph() Grapher 20 | } 21 | 22 | // A Grapher is any type that returns a Grapher, mainly used to identify 23 | // dag.Graph and dag.AcyclicGraph. In the case of Graph and AcyclicGraph, they 24 | // return themselves. 25 | type Grapher interface { 26 | DirectedGraph() Grapher 27 | } 28 | 29 | // Vertex of the graph. 30 | type Vertex interface{} 31 | 32 | // NamedVertex is an optional interface that can be implemented by Vertex 33 | // to give it a human-friendly name that is used for outputting the graph. 34 | type NamedVertex interface { 35 | Vertex 36 | Name() string 37 | } 38 | 39 | func (g *Graph) DirectedGraph() Grapher { 40 | return g 41 | } 42 | 43 | // Vertices returns the list of all the vertices in the graph. 44 | func (g *Graph) Vertices() []Vertex { 45 | result := make([]Vertex, 0, len(g.vertices)) 46 | for _, v := range g.vertices { 47 | result = append(result, v.(Vertex)) 48 | } 49 | 50 | return result 51 | } 52 | 53 | // Edges returns the list of all the edges in the graph. 54 | func (g *Graph) Edges() []Edge { 55 | result := make([]Edge, 0, len(g.edges)) 56 | for _, v := range g.edges { 57 | result = append(result, v.(Edge)) 58 | } 59 | 60 | return result 61 | } 62 | 63 | // EdgesFrom returns the list of edges from the given source. 64 | func (g *Graph) EdgesFrom(v Vertex) []Edge { 65 | var result []Edge 66 | from := hashcode(v) 67 | for _, e := range g.Edges() { 68 | if hashcode(e.Source()) == from { 69 | result = append(result, e) 70 | } 71 | } 72 | 73 | return result 74 | } 75 | 76 | // EdgesTo returns the list of edges to the given target. 77 | func (g *Graph) EdgesTo(v Vertex) []Edge { 78 | var result []Edge 79 | search := hashcode(v) 80 | for _, e := range g.Edges() { 81 | if hashcode(e.Target()) == search { 82 | result = append(result, e) 83 | } 84 | } 85 | 86 | return result 87 | } 88 | 89 | // HasVertex checks if the given Vertex is present in the graph. 90 | func (g *Graph) HasVertex(v Vertex) bool { 91 | return g.vertices.Include(v) 92 | } 93 | 94 | // HasEdge checks if the given Edge is present in the graph. 95 | func (g *Graph) HasEdge(e Edge) bool { 96 | return g.edges.Include(e) 97 | } 98 | 99 | // Add adds a vertex to the graph. This is safe to call multiple time with 100 | // the same Vertex. 101 | func (g *Graph) Add(v Vertex) Vertex { 102 | g.init() 103 | g.vertices.Add(v) 104 | return v 105 | } 106 | 107 | // Remove removes a vertex from the graph. This will also remove any 108 | // edges with this vertex as a source or target. 109 | func (g *Graph) Remove(v Vertex) Vertex { 110 | // Delete the vertex itself 111 | g.vertices.Delete(v) 112 | 113 | // Delete the edges to non-existent things 114 | for _, target := range g.downEdgesNoCopy(v) { 115 | g.RemoveEdge(BasicEdge(v, target)) 116 | } 117 | for _, source := range g.upEdgesNoCopy(v) { 118 | g.RemoveEdge(BasicEdge(source, v)) 119 | } 120 | 121 | return nil 122 | } 123 | 124 | // Replace replaces the original Vertex with replacement. If the original 125 | // does not exist within the graph, then false is returned. Otherwise, true 126 | // is returned. 127 | func (g *Graph) Replace(original, replacement Vertex) bool { 128 | // If we don't have the original, we can't do anything 129 | if !g.vertices.Include(original) { 130 | return false 131 | } 132 | 133 | // If they're the same, then don't do anything 134 | if original == replacement { 135 | return true 136 | } 137 | 138 | // Add our new vertex, then copy all the edges 139 | g.Add(replacement) 140 | for _, target := range g.downEdgesNoCopy(original) { 141 | g.Connect(BasicEdge(replacement, target)) 142 | } 143 | for _, source := range g.upEdgesNoCopy(original) { 144 | g.Connect(BasicEdge(source, replacement)) 145 | } 146 | 147 | // Remove our old vertex, which will also remove all the edges 148 | g.Remove(original) 149 | 150 | return true 151 | } 152 | 153 | // RemoveEdge removes an edge from the graph. 154 | func (g *Graph) RemoveEdge(edge Edge) { 155 | g.init() 156 | 157 | // Delete the edge from the set 158 | g.edges.Delete(edge) 159 | 160 | // Delete the up/down edges 161 | if s, ok := g.downEdges[hashcode(edge.Source())]; ok { 162 | s.Delete(edge.Target()) 163 | } 164 | if s, ok := g.upEdges[hashcode(edge.Target())]; ok { 165 | s.Delete(edge.Source()) 166 | } 167 | } 168 | 169 | // UpEdges returns the vertices that are *sources* of edges that target the 170 | // destination Vertex v. 171 | func (g *Graph) UpEdges(v Vertex) Set { 172 | return g.upEdgesNoCopy(v).Copy() 173 | } 174 | 175 | // DownEdges returns the vertices that are *targets* of edges that originate 176 | // from the source Vertex v. 177 | func (g *Graph) DownEdges(v Vertex) Set { 178 | return g.downEdgesNoCopy(v).Copy() 179 | } 180 | 181 | // downEdgesNoCopy returns the vertices targeted by edges from the source Vertex 182 | // v as a Set. This Set is the same as used internally by the Graph to prevent a 183 | // copy, and must not be modified by the caller. 184 | func (g *Graph) downEdgesNoCopy(v Vertex) Set { 185 | g.init() 186 | return g.downEdges[hashcode(v)] 187 | } 188 | 189 | // upEdgesNoCopy returns the vertices that are sources of edges targeting the 190 | // destination Vertex v as a Set. This Set is the same as used internally by the 191 | // Graph to prevent a copy, and must not be modified by the caller. 192 | func (g *Graph) upEdgesNoCopy(v Vertex) Set { 193 | g.init() 194 | return g.upEdges[hashcode(v)] 195 | } 196 | 197 | // Connect adds an edge with the given source and target. This is safe to 198 | // call multiple times with the same value. Note that the same value is 199 | // verified through pointer equality of the vertices, not through the 200 | // value of the edge itself. 201 | func (g *Graph) Connect(edge Edge) { 202 | g.init() 203 | 204 | source := edge.Source() 205 | target := edge.Target() 206 | sourceCode := hashcode(source) 207 | targetCode := hashcode(target) 208 | 209 | // Do we have this already? If so, don't add it again. 210 | if s, ok := g.downEdges[sourceCode]; ok && s.Include(target) { 211 | return 212 | } 213 | 214 | // Add the edge to the set 215 | g.edges.Add(edge) 216 | 217 | // Add the down edge 218 | s, ok := g.downEdges[sourceCode] 219 | if !ok { 220 | s = make(Set) 221 | g.downEdges[sourceCode] = s 222 | } 223 | s.Add(target) 224 | 225 | // Add the up edge 226 | s, ok = g.upEdges[targetCode] 227 | if !ok { 228 | s = make(Set) 229 | g.upEdges[targetCode] = s 230 | } 231 | s.Add(source) 232 | } 233 | 234 | // Subsume imports all of the nodes and edges from the given graph into the 235 | // reciever, leaving the given graph unchanged. 236 | // 237 | // If any of the nodes in the given graph are already present in the reciever 238 | // then the existing node will be retained and any new edges from the given 239 | // graph will be connected with it. 240 | // 241 | // If the given graph has edges in common with the reciever then they will be 242 | // ignored, because each pair of nodes can only be connected once. 243 | func (g *Graph) Subsume(other *Graph) { 244 | // We're using Set.Filter just as a "visit each element" here, so we're 245 | // not doing anything with the result (which will always be empty). 246 | other.vertices.Filter(func(i interface{}) bool { 247 | g.Add(i) 248 | return false 249 | }) 250 | other.edges.Filter(func(i interface{}) bool { 251 | g.Connect(i.(Edge)) 252 | return false 253 | }) 254 | } 255 | 256 | // String outputs some human-friendly output for the graph structure. 257 | func (g *Graph) StringWithNodeTypes() string { 258 | var buf bytes.Buffer 259 | 260 | // Build the list of node names and a mapping so that we can more 261 | // easily alphabetize the output to remain deterministic. 262 | vertices := g.Vertices() 263 | names := make([]string, 0, len(vertices)) 264 | mapping := make(map[string]Vertex, len(vertices)) 265 | for _, v := range vertices { 266 | name := VertexName(v) 267 | names = append(names, name) 268 | mapping[name] = v 269 | } 270 | sort.Strings(names) 271 | 272 | // Write each node in order... 273 | for _, name := range names { 274 | v := mapping[name] 275 | targets := g.downEdges[hashcode(v)] 276 | 277 | buf.WriteString(fmt.Sprintf("%s - %T\n", name, v)) 278 | 279 | // Alphabetize dependencies 280 | deps := make([]string, 0, targets.Len()) 281 | targetNodes := make(map[string]Vertex) 282 | for _, target := range targets { 283 | dep := VertexName(target) 284 | deps = append(deps, dep) 285 | targetNodes[dep] = target 286 | } 287 | sort.Strings(deps) 288 | 289 | // Write dependencies 290 | for _, d := range deps { 291 | buf.WriteString(fmt.Sprintf(" %s - %T\n", d, targetNodes[d])) 292 | } 293 | } 294 | 295 | return buf.String() 296 | } 297 | 298 | // String outputs some human-friendly output for the graph structure. 299 | func (g *Graph) String() string { 300 | var buf bytes.Buffer 301 | 302 | // Build the list of node names and a mapping so that we can more 303 | // easily alphabetize the output to remain deterministic. 304 | vertices := g.Vertices() 305 | names := make([]string, 0, len(vertices)) 306 | mapping := make(map[string]Vertex, len(vertices)) 307 | for _, v := range vertices { 308 | name := VertexName(v) 309 | names = append(names, name) 310 | mapping[name] = v 311 | } 312 | sort.Strings(names) 313 | 314 | // Write each node in order... 315 | for _, name := range names { 316 | v := mapping[name] 317 | targets := g.downEdges[hashcode(v)] 318 | 319 | buf.WriteString(fmt.Sprintf("%s\n", name)) 320 | 321 | // Alphabetize dependencies 322 | deps := make([]string, 0, targets.Len()) 323 | for _, target := range targets { 324 | deps = append(deps, VertexName(target)) 325 | } 326 | sort.Strings(deps) 327 | 328 | // Write dependencies 329 | for _, d := range deps { 330 | buf.WriteString(fmt.Sprintf(" %s\n", d)) 331 | } 332 | } 333 | 334 | return buf.String() 335 | } 336 | 337 | func (g *Graph) init() { 338 | if g.vertices == nil { 339 | g.vertices = make(Set) 340 | } 341 | if g.edges == nil { 342 | g.edges = make(Set) 343 | } 344 | if g.downEdges == nil { 345 | g.downEdges = make(map[interface{}]Set) 346 | } 347 | if g.upEdges == nil { 348 | g.upEdges = make(map[interface{}]Set) 349 | } 350 | } 351 | 352 | // Dot returns a dot-formatted representation of the Graph. 353 | func (g *Graph) Dot(opts *DotOpts) []byte { 354 | return newMarshalGraph("", g).Dot(opts) 355 | } 356 | 357 | // VertexName returns the name of a vertex. 358 | func VertexName(raw Vertex) string { 359 | switch v := raw.(type) { 360 | case NamedVertex: 361 | return v.Name() 362 | case fmt.Stringer: 363 | return v.String() 364 | default: 365 | return fmt.Sprintf("%v", v) 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /dag/dag.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sort" 7 | "strings" 8 | ) 9 | 10 | // AcyclicGraph is a specialization of Graph that cannot have cycles. 11 | type AcyclicGraph struct { 12 | Graph 13 | } 14 | 15 | // WalkFunc is the callback used for walking the graph. 16 | // The return error could be a "bag of errors", learn more from 17 | // https://docs.sourcegraph.com/dev/background-information/languages/go_errors#use-of-errors-multierror 18 | type WalkFunc func(Vertex) error 19 | 20 | // DepthWalkFunc is a walk function that also receives the current depth of the 21 | // walk as an argument 22 | type DepthWalkFunc func(Vertex, int) error 23 | 24 | func (g *AcyclicGraph) DirectedGraph() Grapher { 25 | return g 26 | } 27 | 28 | // Returns a Set that includes every Vertex yielded by walking down from the 29 | // provided starting Vertex v. 30 | func (g *AcyclicGraph) Ancestors(v Vertex) (Set, error) { 31 | s := make(Set) 32 | memoFunc := func(v Vertex, d int) error { 33 | s.Add(v) 34 | return nil 35 | } 36 | 37 | if err := g.DepthFirstWalk(g.downEdgesNoCopy(v), memoFunc); err != nil { 38 | return nil, err 39 | } 40 | 41 | return s, nil 42 | } 43 | 44 | // Returns a Set that includes every Vertex yielded by walking up from the 45 | // provided starting Vertex v. 46 | func (g *AcyclicGraph) Descendents(v Vertex) (Set, error) { 47 | s := make(Set) 48 | memoFunc := func(v Vertex, d int) error { 49 | s.Add(v) 50 | return nil 51 | } 52 | 53 | if err := g.ReverseDepthFirstWalk(g.upEdgesNoCopy(v), memoFunc); err != nil { 54 | return nil, err 55 | } 56 | 57 | return s, nil 58 | } 59 | 60 | // Root returns the root of the DAG, or an error. 61 | // 62 | // Complexity: O(V) 63 | func (g *AcyclicGraph) Root() (Vertex, error) { 64 | roots := make([]Vertex, 0, 1) 65 | for _, v := range g.Vertices() { 66 | if g.upEdgesNoCopy(v).Len() == 0 { 67 | roots = append(roots, v) 68 | } 69 | } 70 | 71 | if len(roots) > 1 { 72 | // TODO(mitchellh): make this error message a lot better 73 | return nil, fmt.Errorf("multiple roots: %#v", roots) 74 | } 75 | 76 | if len(roots) == 0 { 77 | return nil, fmt.Errorf("no roots found") 78 | } 79 | 80 | return roots[0], nil 81 | } 82 | 83 | // TransitiveReduction performs the transitive reduction of graph g in place. 84 | // The transitive reduction of a graph is a graph with as few edges as 85 | // possible with the same reachability as the original graph. This means 86 | // that if there are three nodes A => B => C, and A connects to both 87 | // B and C, and B connects to C, then the transitive reduction is the 88 | // same graph with only a single edge between A and B, and a single edge 89 | // between B and C. 90 | // 91 | // The graph must be free of cycles for this operation to behave properly. 92 | // 93 | // Complexity: O(V(V+E)), or asymptotically O(VE) 94 | func (g *AcyclicGraph) TransitiveReduction() { 95 | // For each vertex u in graph g, do a DFS starting from each vertex 96 | // v such that the edge (u,v) exists (v is a direct descendant of u). 97 | // 98 | // For each v-prime reachable from v, remove the edge (u, v-prime). 99 | for _, u := range g.Vertices() { 100 | uTargets := g.downEdgesNoCopy(u) 101 | 102 | g.DepthFirstWalk(g.downEdgesNoCopy(u), func(v Vertex, d int) error { 103 | shared := uTargets.Intersection(g.downEdgesNoCopy(v)) 104 | for _, vPrime := range shared { 105 | g.RemoveEdge(BasicEdge(u, vPrime)) 106 | } 107 | 108 | return nil 109 | }) 110 | } 111 | } 112 | 113 | // Validate validates the DAG. A DAG is valid if it has a single root 114 | // with no cycles. 115 | func (g *AcyclicGraph) Validate() error { 116 | if _, err := g.Root(); err != nil { 117 | return err 118 | } 119 | 120 | // Look for cycles of more than 1 component 121 | var err error 122 | cycles := g.Cycles() 123 | if len(cycles) > 0 { 124 | for _, cycle := range cycles { 125 | cycleStr := make([]string, len(cycle)) 126 | for j, vertex := range cycle { 127 | cycleStr[j] = VertexName(vertex) 128 | } 129 | 130 | err = errors.Join(err, fmt.Errorf( 131 | "Cycle: %s", strings.Join(cycleStr, ", "))) 132 | } 133 | } 134 | 135 | // Look for cycles to self 136 | for _, e := range g.Edges() { 137 | if e.Source() == e.Target() { 138 | err = errors.Join(err, fmt.Errorf( 139 | "Self reference: %s", VertexName(e.Source()))) 140 | } 141 | } 142 | 143 | return err 144 | } 145 | 146 | // Cycles reports any cycles between graph nodes. 147 | // Self-referencing nodes are not reported, and must be detected separately. 148 | func (g *AcyclicGraph) Cycles() [][]Vertex { 149 | var cycles [][]Vertex 150 | for _, cycle := range StronglyConnected(&g.Graph) { 151 | if len(cycle) > 1 { 152 | cycles = append(cycles, cycle) 153 | } 154 | } 155 | return cycles 156 | } 157 | 158 | // Walk walks the graph, calling your callback as each node is visited. 159 | // This will walk nodes in parallel if it can. The resulting diagnostics 160 | // contains problems from all graphs visited, in no particular order. 161 | func (g *AcyclicGraph) Walk(cb WalkFunc) error { 162 | w := &Walker{Callback: cb, Reverse: true} 163 | w.Update(g) 164 | return w.Wait() 165 | } 166 | 167 | // simple convenience helper for converting a dag.Set to a []Vertex 168 | func AsVertexList(s Set) []Vertex { 169 | vertexList := make([]Vertex, 0, len(s)) 170 | for _, raw := range s { 171 | vertexList = append(vertexList, raw.(Vertex)) 172 | } 173 | return vertexList 174 | } 175 | 176 | type vertexAtDepth struct { 177 | Vertex Vertex 178 | Depth int 179 | } 180 | 181 | // TopologicalOrder returns a topological sort of the given graph, with source 182 | // vertices ordered before the targets of their edges. The nodes are not sorted, 183 | // and any valid order may be returned. This function will panic if it 184 | // encounters a cycle. 185 | func (g *AcyclicGraph) TopologicalOrder() []Vertex { 186 | return g.topoOrder(upOrder) 187 | } 188 | 189 | // ReverseTopologicalOrder returns a topological sort of the given graph, with 190 | // target vertices ordered before the sources of their edges. The nodes are not 191 | // sorted, and any valid order may be returned. This function will panic if it 192 | // encounters a cycle. 193 | func (g *AcyclicGraph) ReverseTopologicalOrder() []Vertex { 194 | return g.topoOrder(downOrder) 195 | } 196 | 197 | func (g *AcyclicGraph) topoOrder(order walkType) []Vertex { 198 | // Use a dfs-based sorting algorithm, similar to that used in 199 | // TransitiveReduction. 200 | sorted := make([]Vertex, 0, len(g.vertices)) 201 | 202 | // tmp track the current working node to check for cycles 203 | tmp := map[Vertex]bool{} 204 | 205 | // perm tracks completed nodes to end the recursion 206 | perm := map[Vertex]bool{} 207 | 208 | var visit func(v Vertex) 209 | 210 | visit = func(v Vertex) { 211 | if perm[v] { 212 | return 213 | } 214 | 215 | if tmp[v] { 216 | panic("cycle found in dag") 217 | } 218 | 219 | tmp[v] = true 220 | var next Set 221 | switch { 222 | case order&downOrder != 0: 223 | next = g.downEdgesNoCopy(v) 224 | case order&upOrder != 0: 225 | next = g.upEdgesNoCopy(v) 226 | default: 227 | panic(fmt.Sprintln("invalid order", order)) 228 | } 229 | 230 | for _, u := range next { 231 | visit(u) 232 | } 233 | 234 | tmp[v] = false 235 | perm[v] = true 236 | sorted = append(sorted, v) 237 | } 238 | 239 | for _, v := range g.Vertices() { 240 | visit(v) 241 | } 242 | 243 | return sorted 244 | } 245 | 246 | type walkType uint64 247 | 248 | const ( 249 | depthFirst walkType = 1 << iota 250 | breadthFirst 251 | downOrder 252 | upOrder 253 | ) 254 | 255 | // DepthFirstWalk does a depth-first walk of the graph starting from 256 | // the vertices in start. 257 | func (g *AcyclicGraph) DepthFirstWalk(start Set, f DepthWalkFunc) error { 258 | return g.walk(depthFirst|downOrder, false, start, f) 259 | } 260 | 261 | // ReverseDepthFirstWalk does a depth-first walk _up_ the graph starting from 262 | // the vertices in start. 263 | func (g *AcyclicGraph) ReverseDepthFirstWalk(start Set, f DepthWalkFunc) error { 264 | return g.walk(depthFirst|upOrder, false, start, f) 265 | } 266 | 267 | // BreadthFirstWalk does a breadth-first walk of the graph starting from 268 | // the vertices in start. 269 | func (g *AcyclicGraph) BreadthFirstWalk(start Set, f DepthWalkFunc) error { 270 | return g.walk(breadthFirst|downOrder, false, start, f) 271 | } 272 | 273 | // ReverseBreadthFirstWalk does a breadth-first walk _up_ the graph starting from 274 | // the vertices in start. 275 | func (g *AcyclicGraph) ReverseBreadthFirstWalk(start Set, f DepthWalkFunc) error { 276 | return g.walk(breadthFirst|upOrder, false, start, f) 277 | } 278 | 279 | // Setting test to true will walk sets of vertices in sorted order for 280 | // deterministic testing. 281 | func (g *AcyclicGraph) walk(order walkType, test bool, start Set, f DepthWalkFunc) error { 282 | seen := make(map[Vertex]struct{}) 283 | frontier := make([]vertexAtDepth, 0, len(start)) 284 | for _, v := range start { 285 | frontier = append(frontier, vertexAtDepth{ 286 | Vertex: v, 287 | Depth: 0, 288 | }) 289 | } 290 | 291 | if test { 292 | testSortFrontier(frontier) 293 | } 294 | 295 | for len(frontier) > 0 { 296 | // Pop the current vertex 297 | var current vertexAtDepth 298 | 299 | switch { 300 | case order&depthFirst != 0: 301 | // depth first, the frontier is used like a stack 302 | n := len(frontier) 303 | current = frontier[n-1] 304 | frontier = frontier[:n-1] 305 | case order&breadthFirst != 0: 306 | // breadth first, the frontier is used like a queue 307 | current = frontier[0] 308 | frontier = frontier[1:] 309 | default: 310 | panic(fmt.Sprint("invalid visit order", order)) 311 | } 312 | 313 | // Check if we've seen this already and return... 314 | if _, ok := seen[current.Vertex]; ok { 315 | continue 316 | } 317 | seen[current.Vertex] = struct{}{} 318 | 319 | // Visit the current node 320 | if err := f(current.Vertex, current.Depth); err != nil { 321 | return err 322 | } 323 | 324 | var edges Set 325 | switch { 326 | case order&downOrder != 0: 327 | edges = g.downEdgesNoCopy(current.Vertex) 328 | case order&upOrder != 0: 329 | edges = g.upEdgesNoCopy(current.Vertex) 330 | default: 331 | panic(fmt.Sprint("invalid walk order", order)) 332 | } 333 | 334 | if test { 335 | frontier = testAppendNextSorted(frontier, edges, current.Depth+1) 336 | } else { 337 | frontier = appendNext(frontier, edges, current.Depth+1) 338 | } 339 | } 340 | return nil 341 | } 342 | 343 | func appendNext(frontier []vertexAtDepth, next Set, depth int) []vertexAtDepth { 344 | for _, v := range next { 345 | frontier = append(frontier, vertexAtDepth{ 346 | Vertex: v, 347 | Depth: depth, 348 | }) 349 | } 350 | return frontier 351 | } 352 | 353 | func testAppendNextSorted(frontier []vertexAtDepth, edges Set, depth int) []vertexAtDepth { 354 | var newEdges []vertexAtDepth 355 | for _, v := range edges { 356 | newEdges = append(newEdges, vertexAtDepth{ 357 | Vertex: v, 358 | Depth: depth, 359 | }) 360 | } 361 | testSortFrontier(newEdges) 362 | return append(frontier, newEdges...) 363 | } 364 | func testSortFrontier(f []vertexAtDepth) { 365 | sort.Slice(f, func(i, j int) bool { 366 | return VertexName(f[i].Vertex) < VertexName(f[j].Vertex) 367 | }) 368 | } 369 | -------------------------------------------------------------------------------- /dag/dag_test.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "reflect" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | "testing" 12 | ) 13 | 14 | func TestMain(m *testing.M) { 15 | flag.Parse() 16 | os.Exit(m.Run()) 17 | } 18 | 19 | func TestAcyclicGraphRoot(t *testing.T) { 20 | var g AcyclicGraph 21 | g.Add(1) 22 | g.Add(2) 23 | g.Add(3) 24 | g.Connect(BasicEdge(3, 2)) 25 | g.Connect(BasicEdge(3, 1)) 26 | 27 | if root, err := g.Root(); err != nil { 28 | t.Fatalf("err: %s", err) 29 | } else if root != 3 { 30 | t.Fatalf("bad: %#v", root) 31 | } 32 | } 33 | 34 | func TestAcyclicGraphRoot_cycle(t *testing.T) { 35 | var g AcyclicGraph 36 | g.Add(1) 37 | g.Add(2) 38 | g.Add(3) 39 | g.Connect(BasicEdge(1, 2)) 40 | g.Connect(BasicEdge(2, 3)) 41 | g.Connect(BasicEdge(3, 1)) 42 | 43 | if _, err := g.Root(); err == nil { 44 | t.Fatal("should error") 45 | } 46 | } 47 | 48 | func TestAcyclicGraphRoot_multiple(t *testing.T) { 49 | var g AcyclicGraph 50 | g.Add(1) 51 | g.Add(2) 52 | g.Add(3) 53 | g.Connect(BasicEdge(3, 2)) 54 | 55 | if _, err := g.Root(); err == nil { 56 | t.Fatal("should error") 57 | } 58 | } 59 | 60 | func TestAyclicGraphTransReduction(t *testing.T) { 61 | var g AcyclicGraph 62 | g.Add(1) 63 | g.Add(2) 64 | g.Add(3) 65 | g.Connect(BasicEdge(1, 2)) 66 | g.Connect(BasicEdge(1, 3)) 67 | g.Connect(BasicEdge(2, 3)) 68 | g.TransitiveReduction() 69 | 70 | actual := strings.TrimSpace(g.String()) 71 | expected := strings.TrimSpace(testGraphTransReductionStr) 72 | if actual != expected { 73 | t.Fatalf("bad: %s", actual) 74 | } 75 | } 76 | 77 | func TestAyclicGraphTransReduction_more(t *testing.T) { 78 | var g AcyclicGraph 79 | g.Add(1) 80 | g.Add(2) 81 | g.Add(3) 82 | g.Add(4) 83 | g.Connect(BasicEdge(1, 2)) 84 | g.Connect(BasicEdge(1, 3)) 85 | g.Connect(BasicEdge(1, 4)) 86 | g.Connect(BasicEdge(2, 3)) 87 | g.Connect(BasicEdge(2, 4)) 88 | g.Connect(BasicEdge(3, 4)) 89 | g.TransitiveReduction() 90 | 91 | actual := strings.TrimSpace(g.String()) 92 | expected := strings.TrimSpace(testGraphTransReductionMoreStr) 93 | if actual != expected { 94 | t.Fatalf("bad: %s", actual) 95 | } 96 | } 97 | 98 | func TestAyclicGraphTransReduction_multipleRoots(t *testing.T) { 99 | var g AcyclicGraph 100 | g.Add(1) 101 | g.Add(2) 102 | g.Add(3) 103 | g.Add(4) 104 | g.Connect(BasicEdge(1, 2)) 105 | g.Connect(BasicEdge(1, 3)) 106 | g.Connect(BasicEdge(1, 4)) 107 | g.Connect(BasicEdge(2, 3)) 108 | g.Connect(BasicEdge(2, 4)) 109 | g.Connect(BasicEdge(3, 4)) 110 | 111 | g.Add(5) 112 | g.Add(6) 113 | g.Add(7) 114 | g.Add(8) 115 | g.Connect(BasicEdge(5, 6)) 116 | g.Connect(BasicEdge(5, 7)) 117 | g.Connect(BasicEdge(5, 8)) 118 | g.Connect(BasicEdge(6, 7)) 119 | g.Connect(BasicEdge(6, 8)) 120 | g.Connect(BasicEdge(7, 8)) 121 | g.TransitiveReduction() 122 | 123 | actual := strings.TrimSpace(g.String()) 124 | expected := strings.TrimSpace(testGraphTransReductionMultipleRootsStr) 125 | if actual != expected { 126 | t.Fatalf("bad: %s", actual) 127 | } 128 | } 129 | 130 | // use this to simulate slow sort operations 131 | type counter struct { 132 | Name string 133 | Calls int64 134 | } 135 | 136 | func (s *counter) String() string { 137 | s.Calls++ 138 | return s.Name 139 | } 140 | 141 | // Make sure we can reduce a sizable, fully-connected graph. 142 | func TestAyclicGraphTransReduction_fullyConnected(t *testing.T) { 143 | var g AcyclicGraph 144 | 145 | const nodeCount = 200 146 | nodes := make([]*counter, nodeCount) 147 | for i := 0; i < nodeCount; i++ { 148 | nodes[i] = &counter{Name: strconv.Itoa(i)} 149 | } 150 | 151 | // Add them all to the graph 152 | for _, n := range nodes { 153 | g.Add(n) 154 | } 155 | 156 | // connect them all 157 | for i := range nodes { 158 | for j := range nodes { 159 | if i == j { 160 | continue 161 | } 162 | g.Connect(BasicEdge(nodes[i], nodes[j])) 163 | } 164 | } 165 | 166 | g.TransitiveReduction() 167 | 168 | vertexNameCalls := int64(0) 169 | for _, n := range nodes { 170 | vertexNameCalls += n.Calls 171 | } 172 | 173 | switch { 174 | case vertexNameCalls > 2*nodeCount: 175 | // Make calling it more the 2x per node fatal. 176 | // If we were sorting this would give us roughly ln(n)(n^3) calls, or 177 | // >59000000 calls for 200 vertices. 178 | t.Fatalf("VertexName called %d times", vertexNameCalls) 179 | case vertexNameCalls > 0: 180 | // we don't expect any calls, but a change here isn't necessarily fatal 181 | t.Logf("WARNING: VertexName called %d times", vertexNameCalls) 182 | } 183 | } 184 | 185 | func TestAcyclicGraphValidate(t *testing.T) { 186 | var g AcyclicGraph 187 | g.Add(1) 188 | g.Add(2) 189 | g.Add(3) 190 | g.Connect(BasicEdge(3, 2)) 191 | g.Connect(BasicEdge(3, 1)) 192 | 193 | if err := g.Validate(); err != nil { 194 | t.Fatalf("err: %s", err) 195 | } 196 | } 197 | 198 | func TestAcyclicGraphValidate_cycle(t *testing.T) { 199 | var g AcyclicGraph 200 | g.Add(1) 201 | g.Add(2) 202 | g.Add(3) 203 | g.Connect(BasicEdge(3, 2)) 204 | g.Connect(BasicEdge(3, 1)) 205 | g.Connect(BasicEdge(1, 2)) 206 | g.Connect(BasicEdge(2, 1)) 207 | 208 | if err := g.Validate(); err == nil { 209 | t.Fatal("should error") 210 | } 211 | } 212 | 213 | func TestAcyclicGraphValidate_cycleSelf(t *testing.T) { 214 | var g AcyclicGraph 215 | g.Add(1) 216 | g.Add(2) 217 | g.Connect(BasicEdge(1, 1)) 218 | 219 | if err := g.Validate(); err == nil { 220 | t.Fatal("should error") 221 | } 222 | } 223 | 224 | func TestAcyclicGraphAncestors(t *testing.T) { 225 | var g AcyclicGraph 226 | g.Add(1) 227 | g.Add(2) 228 | g.Add(3) 229 | g.Add(4) 230 | g.Add(5) 231 | g.Connect(BasicEdge(0, 1)) 232 | g.Connect(BasicEdge(1, 2)) 233 | g.Connect(BasicEdge(2, 3)) 234 | g.Connect(BasicEdge(3, 4)) 235 | g.Connect(BasicEdge(4, 5)) 236 | 237 | actual, err := g.Ancestors(2) 238 | if err != nil { 239 | t.Fatalf("err: %#v", err) 240 | } 241 | 242 | expected := []Vertex{3, 4, 5} 243 | 244 | if actual.Len() != len(expected) { 245 | t.Fatalf("bad length! expected %#v to have len %d", actual, len(expected)) 246 | } 247 | 248 | for _, e := range expected { 249 | if !actual.Include(e) { 250 | t.Fatalf("expected: %#v to include: %#v", expected, actual) 251 | } 252 | } 253 | } 254 | 255 | func TestAcyclicGraphDescendents(t *testing.T) { 256 | var g AcyclicGraph 257 | g.Add(1) 258 | g.Add(2) 259 | g.Add(3) 260 | g.Add(4) 261 | g.Add(5) 262 | g.Connect(BasicEdge(0, 1)) 263 | g.Connect(BasicEdge(1, 2)) 264 | g.Connect(BasicEdge(2, 3)) 265 | g.Connect(BasicEdge(3, 4)) 266 | g.Connect(BasicEdge(4, 5)) 267 | 268 | actual, err := g.Descendents(2) 269 | if err != nil { 270 | t.Fatalf("err: %#v", err) 271 | } 272 | 273 | expected := []Vertex{0, 1} 274 | 275 | if actual.Len() != len(expected) { 276 | t.Fatalf("bad length! expected %#v to have len %d", actual, len(expected)) 277 | } 278 | 279 | for _, e := range expected { 280 | if !actual.Include(e) { 281 | t.Fatalf("expected: %#v to include: %#v", expected, actual) 282 | } 283 | } 284 | } 285 | 286 | func TestAcyclicGraphWalk(t *testing.T) { 287 | var g AcyclicGraph 288 | g.Add(1) 289 | g.Add(2) 290 | g.Add(3) 291 | g.Connect(BasicEdge(3, 2)) 292 | g.Connect(BasicEdge(3, 1)) 293 | 294 | var visits []Vertex 295 | var lock sync.Mutex 296 | err := g.Walk(func(v Vertex) error { 297 | lock.Lock() 298 | defer lock.Unlock() 299 | visits = append(visits, v) 300 | return nil 301 | }) 302 | if err != nil { 303 | t.Fatalf("err: %s", err) 304 | } 305 | 306 | expected := [][]Vertex{ 307 | {1, 2, 3}, 308 | {2, 1, 3}, 309 | } 310 | for _, e := range expected { 311 | if reflect.DeepEqual(visits, e) { 312 | return 313 | } 314 | } 315 | 316 | t.Fatalf("bad: %#v", visits) 317 | } 318 | 319 | func TestAcyclicGraphWalk_error(t *testing.T) { 320 | var g AcyclicGraph 321 | g.Add(1) 322 | g.Add(2) 323 | g.Add(3) 324 | g.Add(4) 325 | g.Connect(BasicEdge(4, 3)) 326 | g.Connect(BasicEdge(3, 2)) 327 | g.Connect(BasicEdge(2, 1)) 328 | 329 | var visits []Vertex 330 | var lock sync.Mutex 331 | err := g.Walk(func(v Vertex) error { 332 | lock.Lock() 333 | defer lock.Unlock() 334 | 335 | if v == 2 { 336 | return fmt.Errorf("error") 337 | } 338 | 339 | visits = append(visits, v) 340 | return nil 341 | }) 342 | if err == nil { 343 | t.Fatal("should error") 344 | } 345 | 346 | expected := []Vertex{1} 347 | if !reflect.DeepEqual(visits, expected) { 348 | t.Errorf("wrong visits\ngot: %#v\nwant: %#v", visits, expected) 349 | } 350 | 351 | } 352 | 353 | func BenchmarkDAG(b *testing.B) { 354 | for i := 0; i < b.N; i++ { 355 | count := 150 356 | b.StopTimer() 357 | g := &AcyclicGraph{} 358 | 359 | // create 4 layers of fully connected nodes 360 | // layer A 361 | for i := 0; i < count; i++ { 362 | g.Add(fmt.Sprintf("A%d", i)) 363 | } 364 | 365 | // layer B 366 | for i := 0; i < count; i++ { 367 | B := fmt.Sprintf("B%d", i) 368 | g.Add(B) 369 | for j := 0; j < count; j++ { 370 | g.Connect(BasicEdge(B, fmt.Sprintf("A%d", j))) 371 | } 372 | } 373 | 374 | // layer C 375 | for i := 0; i < count; i++ { 376 | c := fmt.Sprintf("C%d", i) 377 | g.Add(c) 378 | for j := 0; j < count; j++ { 379 | // connect them to previous layers so we have something that requires reduction 380 | g.Connect(BasicEdge(c, fmt.Sprintf("A%d", j))) 381 | g.Connect(BasicEdge(c, fmt.Sprintf("B%d", j))) 382 | } 383 | } 384 | 385 | // layer D 386 | for i := 0; i < count; i++ { 387 | d := fmt.Sprintf("D%d", i) 388 | g.Add(d) 389 | for j := 0; j < count; j++ { 390 | g.Connect(BasicEdge(d, fmt.Sprintf("A%d", j))) 391 | g.Connect(BasicEdge(d, fmt.Sprintf("B%d", j))) 392 | g.Connect(BasicEdge(d, fmt.Sprintf("C%d", j))) 393 | } 394 | } 395 | 396 | b.StartTimer() 397 | // Find dependencies for every node 398 | for _, v := range g.Vertices() { 399 | _, err := g.Ancestors(v) 400 | if err != nil { 401 | b.Fatal(err) 402 | } 403 | } 404 | 405 | // reduce the final graph 406 | g.TransitiveReduction() 407 | } 408 | } 409 | 410 | func TestAcyclicGraphWalkOrder(t *testing.T) { 411 | /* Sample dependency graph, 412 | all edges pointing downwards. 413 | 1 2 414 | / \ / \ 415 | 3 4 5 416 | / \ / 417 | 6 7 418 | / | \ 419 | 8 9 10 420 | \ | / 421 | 11 422 | */ 423 | 424 | var g AcyclicGraph 425 | for i := 1; i <= 11; i++ { 426 | g.Add(i) 427 | } 428 | g.Connect(BasicEdge(1, 3)) 429 | g.Connect(BasicEdge(1, 4)) 430 | g.Connect(BasicEdge(2, 4)) 431 | g.Connect(BasicEdge(2, 5)) 432 | g.Connect(BasicEdge(3, 6)) 433 | g.Connect(BasicEdge(4, 7)) 434 | g.Connect(BasicEdge(5, 7)) 435 | g.Connect(BasicEdge(7, 8)) 436 | g.Connect(BasicEdge(7, 9)) 437 | g.Connect(BasicEdge(7, 10)) 438 | g.Connect(BasicEdge(8, 11)) 439 | g.Connect(BasicEdge(9, 11)) 440 | g.Connect(BasicEdge(10, 11)) 441 | 442 | start := make(Set) 443 | start.Add(2) 444 | start.Add(1) 445 | reverse := make(Set) 446 | reverse.Add(11) 447 | reverse.Add(6) 448 | 449 | t.Run("DepthFirst", func(t *testing.T) { 450 | var visits []vertexAtDepth 451 | g.walk(depthFirst|downOrder, true, start, func(v Vertex, d int) error { 452 | visits = append(visits, vertexAtDepth{v, d}) 453 | return nil 454 | 455 | }) 456 | expect := []vertexAtDepth{ 457 | {2, 0}, {5, 1}, {7, 2}, {9, 3}, {11, 4}, {8, 3}, {10, 3}, {4, 1}, {1, 0}, {3, 1}, {6, 2}, 458 | } 459 | if !reflect.DeepEqual(visits, expect) { 460 | t.Errorf("expected visits:\n%v\ngot:\n%v\n", expect, visits) 461 | } 462 | }) 463 | t.Run("ReverseDepthFirst", func(t *testing.T) { 464 | var visits []vertexAtDepth 465 | g.walk(depthFirst|upOrder, true, reverse, func(v Vertex, d int) error { 466 | visits = append(visits, vertexAtDepth{v, d}) 467 | return nil 468 | 469 | }) 470 | expect := []vertexAtDepth{ 471 | {6, 0}, {3, 1}, {1, 2}, {11, 0}, {9, 1}, {7, 2}, {5, 3}, {2, 4}, {4, 3}, {8, 1}, {10, 1}, 472 | } 473 | if !reflect.DeepEqual(visits, expect) { 474 | t.Errorf("expected visits:\n%v\ngot:\n%v\n", expect, visits) 475 | } 476 | }) 477 | t.Run("BreadthFirst", func(t *testing.T) { 478 | var visits []vertexAtDepth 479 | g.walk(breadthFirst|downOrder, true, start, func(v Vertex, d int) error { 480 | visits = append(visits, vertexAtDepth{v, d}) 481 | return nil 482 | 483 | }) 484 | expect := []vertexAtDepth{ 485 | {1, 0}, {2, 0}, {3, 1}, {4, 1}, {5, 1}, {6, 2}, {7, 2}, {10, 3}, {8, 3}, {9, 3}, {11, 4}, 486 | } 487 | if !reflect.DeepEqual(visits, expect) { 488 | t.Errorf("expected visits:\n%v\ngot:\n%v\n", expect, visits) 489 | } 490 | }) 491 | t.Run("ReverseBreadthFirst", func(t *testing.T) { 492 | var visits []vertexAtDepth 493 | g.walk(breadthFirst|upOrder, true, reverse, func(v Vertex, d int) error { 494 | visits = append(visits, vertexAtDepth{v, d}) 495 | return nil 496 | 497 | }) 498 | expect := []vertexAtDepth{ 499 | {11, 0}, {6, 0}, {10, 1}, {8, 1}, {9, 1}, {3, 1}, {7, 2}, {1, 2}, {4, 3}, {5, 3}, {2, 4}, 500 | } 501 | if !reflect.DeepEqual(visits, expect) { 502 | t.Errorf("expected visits:\n%v\ngot:\n%v\n", expect, visits) 503 | } 504 | }) 505 | 506 | t.Run("TopologicalOrder", func(t *testing.T) { 507 | order := g.topoOrder(downOrder) 508 | 509 | // Validate the order by checking it against the initial graph. We only 510 | // need to verify that each node has it's direct dependencies 511 | // satisfied. 512 | completed := map[Vertex]bool{} 513 | for _, v := range order { 514 | deps := g.DownEdges(v) 515 | for _, dep := range deps { 516 | if !completed[dep] { 517 | t.Fatalf("walking node %v, but dependency %v was not yet seen", v, dep) 518 | } 519 | } 520 | completed[v] = true 521 | } 522 | }) 523 | t.Run("ReverseTopologicalOrder", func(t *testing.T) { 524 | order := g.topoOrder(upOrder) 525 | 526 | // Validate the order by checking it against the initial graph. We only 527 | // need to verify that each node has it's direct dependencies 528 | // satisfied. 529 | completed := map[Vertex]bool{} 530 | for _, v := range order { 531 | deps := g.UpEdges(v) 532 | for _, dep := range deps { 533 | if !completed[dep] { 534 | t.Fatalf("walking node %v, but dependency %v was not yet seen", v, dep) 535 | } 536 | } 537 | completed[v] = true 538 | } 539 | }) 540 | } 541 | 542 | const testGraphTransReductionStr = ` 543 | 1 544 | 2 545 | 2 546 | 3 547 | 3 548 | ` 549 | 550 | const testGraphTransReductionMoreStr = ` 551 | 1 552 | 2 553 | 2 554 | 3 555 | 3 556 | 4 557 | 4 558 | ` 559 | 560 | const testGraphTransReductionMultipleRootsStr = ` 561 | 1 562 | 2 563 | 2 564 | 3 565 | 3 566 | 4 567 | 4 568 | 5 569 | 6 570 | 6 571 | 7 572 | 7 573 | 8 574 | 8 575 | ` 576 | -------------------------------------------------------------------------------- /dag/walk.go: -------------------------------------------------------------------------------- 1 | package dag 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Walker is used to walk every vertex of a graph in parallel. 10 | // 11 | // A vertex will only be walked when the dependencies of that vertex have 12 | // been walked. If two vertices can be walked at the same time, they will be. 13 | // 14 | // Update can be called to update the graph. This can be called even during 15 | // a walk, changing vertices/edges mid-walk. This should be done carefully. 16 | // If a vertex is removed but has already been executed, the result of that 17 | // execution (any error) is still returned by Wait. Changing or re-adding 18 | // a vertex that has already executed has no effect. Changing edges of 19 | // a vertex that has already executed has no effect. 20 | // 21 | // Non-parallelism can be enforced by introducing a lock in your callback 22 | // function. However, the goroutine overhead of a walk will remain. 23 | // Walker will create V*2 goroutines (one for each vertex, and dependency 24 | // waiter for each vertex). In general this should be of no concern unless 25 | // there are a huge number of vertices. 26 | // 27 | // The walk is depth first by default. This can be changed with the Reverse 28 | // option. 29 | // 30 | // A single walker is only valid for one graph walk. After the walk is complete 31 | // you must construct a new walker to walk again. State for the walk is never 32 | // deleted in case vertices or edges are changed. 33 | type Walker struct { 34 | // Callback is what is called for each vertex 35 | Callback WalkFunc 36 | 37 | // Reverse, if true, causes the source of an edge to depend on a target. 38 | // When false (default), the target depends on the source. 39 | Reverse bool 40 | 41 | // changeLock must be held to modify any of the fields below. Only Update 42 | // should modify these fields. Modifying them outside of Update can cause 43 | // serious problems. 44 | changeLock sync.Mutex 45 | vertices Set 46 | edges Set 47 | vertexMap map[Vertex]*walkerVertex 48 | 49 | // wait is done when all vertices have executed. It may become "undone" 50 | // if new vertices are added. 51 | wait sync.WaitGroup 52 | 53 | // errorsMap contains the diagnostics recorded so far for execution, 54 | // and upstreamFailed contains all the vertices whose problems were 55 | // caused by upstream failures, and thus whose diagnostics should be 56 | // excluded from the final set. 57 | // 58 | // Readers and writers of either map must hold diagsLock. 59 | errorsMap map[Vertex]error 60 | upstreamFailed map[Vertex]struct{} 61 | diagsLock sync.Mutex 62 | } 63 | 64 | func (w *Walker) init() { 65 | if w.vertices == nil { 66 | w.vertices = make(Set) 67 | } 68 | if w.edges == nil { 69 | w.edges = make(Set) 70 | } 71 | } 72 | 73 | type walkerVertex struct { 74 | // These should only be set once on initialization and never written again. 75 | // They are not protected by a lock since they don't need to be since 76 | // they are write-once. 77 | 78 | // DoneCh is closed when this vertex has completed execution, regardless 79 | // of success. 80 | // 81 | // CancelCh is closed when the vertex should cancel execution. If execution 82 | // is already complete (DoneCh is closed), this has no effect. Otherwise, 83 | // execution is cancelled as quickly as possible. 84 | DoneCh chan struct{} 85 | CancelCh chan struct{} 86 | 87 | // Dependency information. Any changes to any of these fields requires 88 | // holding DepsLock. 89 | // 90 | // DepsCh is sent a single value that denotes whether the upstream deps 91 | // were successful (no errors). Any value sent means that the upstream 92 | // dependencies are complete. No other values will ever be sent again. 93 | // 94 | // DepsUpdateCh is closed when there is a new DepsCh set. 95 | DepsCh chan bool 96 | DepsUpdateCh chan struct{} 97 | DepsLock sync.Mutex 98 | 99 | // Below is not safe to read/write in parallel. This behavior is 100 | // enforced by changes only happening in Update. Nothing else should 101 | // ever modify these. 102 | deps map[Vertex]chan struct{} 103 | depsCancelCh chan struct{} 104 | } 105 | 106 | // Wait waits for the completion of the walk and returns diagnostics describing 107 | // any problems that arose. Update should be called to populate the walk with 108 | // vertices and edges prior to calling this. 109 | // 110 | // Wait will return as soon as all currently known vertices are complete. 111 | // If you plan on calling Update with more vertices in the future, you 112 | // should not call Wait until after this is done. 113 | func (w *Walker) Wait() error { 114 | // Wait for completion 115 | w.wait.Wait() 116 | 117 | var err error 118 | w.diagsLock.Lock() 119 | for v, vErrs := range w.errorsMap { 120 | if _, upstream := w.upstreamFailed[v]; upstream { 121 | // Ignore diagnostics for nodes that had failed upstreams, since 122 | // the downstream diagnostics are likely to be redundant. 123 | continue 124 | } 125 | err = errors.Join(err, vErrs) 126 | } 127 | w.diagsLock.Unlock() 128 | 129 | return err 130 | } 131 | 132 | // Update updates the currently executing walk with the given graph. 133 | // This will perform a diff of the vertices and edges and update the walker. 134 | // Already completed vertices remain completed (including any errors during 135 | // their execution). 136 | // 137 | // This returns immediately once the walker is updated; it does not wait 138 | // for completion of the walk. 139 | // 140 | // Multiple Updates can be called in parallel. Update can be called at any 141 | // time during a walk. 142 | func (w *Walker) Update(g *AcyclicGraph) { 143 | w.init() 144 | v := make(Set) 145 | e := make(Set) 146 | if g != nil { 147 | v, e = g.vertices, g.edges 148 | } 149 | 150 | // Grab the change lock so no more updates happen but also so that 151 | // no new vertices are executed during this time since we may be 152 | // removing them. 153 | w.changeLock.Lock() 154 | defer w.changeLock.Unlock() 155 | 156 | // Initialize fields 157 | if w.vertexMap == nil { 158 | w.vertexMap = make(map[Vertex]*walkerVertex) 159 | } 160 | 161 | // Calculate all our sets 162 | newEdges := e.Difference(w.edges) 163 | oldEdges := w.edges.Difference(e) 164 | newVerts := v.Difference(w.vertices) 165 | oldVerts := w.vertices.Difference(v) 166 | 167 | // Add the new vertices 168 | for _, raw := range newVerts { 169 | v := raw.(Vertex) 170 | 171 | // Add to the waitgroup so our walk is not done until everything finishes 172 | w.wait.Add(1) 173 | 174 | // Add to our own set so we know about it already 175 | w.vertices.Add(raw) 176 | 177 | // Initialize the vertex info 178 | info := &walkerVertex{ 179 | DoneCh: make(chan struct{}), 180 | CancelCh: make(chan struct{}), 181 | deps: make(map[Vertex]chan struct{}), 182 | } 183 | 184 | // Add it to the map and kick off the walk 185 | w.vertexMap[v] = info 186 | } 187 | 188 | // Remove the old vertices 189 | for _, raw := range oldVerts { 190 | v := raw.(Vertex) 191 | 192 | // Get the vertex info so we can cancel it 193 | info, ok := w.vertexMap[v] 194 | if !ok { 195 | // This vertex for some reason was never in our map. This 196 | // shouldn't be possible. 197 | continue 198 | } 199 | 200 | // Cancel the vertex 201 | close(info.CancelCh) 202 | 203 | // Delete it out of the map 204 | delete(w.vertexMap, v) 205 | w.vertices.Delete(raw) 206 | } 207 | 208 | // Add the new edges 209 | changedDeps := make(Set) 210 | for _, raw := range newEdges { 211 | edge := raw.(Edge) 212 | waiter, dep := w.edgeParts(edge) 213 | 214 | // Get the info for the waiter 215 | waiterInfo, ok := w.vertexMap[waiter] 216 | if !ok { 217 | // Vertex doesn't exist... shouldn't be possible but ignore. 218 | continue 219 | } 220 | 221 | // Get the info for the dep 222 | depInfo, ok := w.vertexMap[dep] 223 | if !ok { 224 | // Vertex doesn't exist... shouldn't be possible but ignore. 225 | continue 226 | } 227 | 228 | // Add the dependency to our waiter 229 | waiterInfo.deps[dep] = depInfo.DoneCh 230 | 231 | // Record that the deps changed for this waiter 232 | changedDeps.Add(waiter) 233 | w.edges.Add(raw) 234 | } 235 | 236 | // Process removed edges 237 | for _, raw := range oldEdges { 238 | edge := raw.(Edge) 239 | waiter, dep := w.edgeParts(edge) 240 | 241 | // Get the info for the waiter 242 | waiterInfo, ok := w.vertexMap[waiter] 243 | if !ok { 244 | // Vertex doesn't exist... shouldn't be possible but ignore. 245 | continue 246 | } 247 | 248 | // Delete the dependency from the waiter 249 | delete(waiterInfo.deps, dep) 250 | 251 | // Record that the deps changed for this waiter 252 | changedDeps.Add(waiter) 253 | w.edges.Delete(raw) 254 | } 255 | 256 | // For each vertex with changed dependencies, we need to kick off 257 | // a new waiter and notify the vertex of the changes. 258 | for _, raw := range changedDeps { 259 | v := raw.(Vertex) 260 | info, ok := w.vertexMap[v] 261 | if !ok { 262 | // Vertex doesn't exist... shouldn't be possible but ignore. 263 | continue 264 | } 265 | 266 | // Create a new done channel 267 | doneCh := make(chan bool, 1) 268 | 269 | // Create the channel we close for cancellation 270 | cancelCh := make(chan struct{}) 271 | 272 | // Build a new deps copy 273 | deps := make(map[Vertex]<-chan struct{}) 274 | for k, v := range info.deps { 275 | deps[k] = v 276 | } 277 | 278 | // Update the update channel 279 | info.DepsLock.Lock() 280 | if info.DepsUpdateCh != nil { 281 | close(info.DepsUpdateCh) 282 | } 283 | info.DepsCh = doneCh 284 | info.DepsUpdateCh = make(chan struct{}) 285 | info.DepsLock.Unlock() 286 | 287 | // Cancel the older waiter 288 | if info.depsCancelCh != nil { 289 | close(info.depsCancelCh) 290 | } 291 | info.depsCancelCh = cancelCh 292 | 293 | // Start the waiter 294 | go w.waitDeps(v, deps, doneCh, cancelCh) 295 | } 296 | 297 | // Start all the new vertices. We do this at the end so that all 298 | // the edge waiters and changes are set up above. 299 | for _, raw := range newVerts { 300 | v := raw.(Vertex) 301 | go w.walkVertex(v, w.vertexMap[v]) 302 | } 303 | } 304 | 305 | // edgeParts returns the waiter and the dependency, in that order. 306 | // The waiter is waiting on the dependency. 307 | func (w *Walker) edgeParts(e Edge) (Vertex, Vertex) { 308 | if w.Reverse { 309 | return e.Source(), e.Target() 310 | } 311 | 312 | return e.Target(), e.Source() 313 | } 314 | 315 | // walkVertex walks a single vertex, waiting for any dependencies before 316 | // executing the callback. 317 | func (w *Walker) walkVertex(v Vertex, info *walkerVertex) { 318 | // When we're done executing, lower the waitgroup count 319 | defer w.wait.Done() 320 | 321 | // When we're done, always close our done channel 322 | defer close(info.DoneCh) 323 | 324 | // Wait for our dependencies. We create a [closed] deps channel so 325 | // that we can immediately fall through to load our actual DepsCh. 326 | var depsSuccess bool 327 | var depsUpdateCh chan struct{} 328 | depsCh := make(chan bool, 1) 329 | depsCh <- true 330 | close(depsCh) 331 | for { 332 | select { 333 | case <-info.CancelCh: 334 | // Cancel 335 | return 336 | 337 | case depsSuccess = <-depsCh: 338 | // Deps complete! Mark as nil to trigger completion handling. 339 | depsCh = nil 340 | 341 | case <-depsUpdateCh: 342 | // New deps, reloop 343 | } 344 | 345 | // Check if we have updated dependencies. This can happen if the 346 | // dependencies were satisfied exactly prior to an Update occurring. 347 | // In that case, we'd like to take into account new dependencies 348 | // if possible. 349 | info.DepsLock.Lock() 350 | if info.DepsCh != nil { 351 | depsCh = info.DepsCh 352 | info.DepsCh = nil 353 | } 354 | if info.DepsUpdateCh != nil { 355 | depsUpdateCh = info.DepsUpdateCh 356 | } 357 | info.DepsLock.Unlock() 358 | 359 | // If we still have no deps channel set, then we're done! 360 | if depsCh == nil { 361 | break 362 | } 363 | } 364 | 365 | // If we passed dependencies, we just want to check once more that 366 | // we're not cancelled, since this can happen just as dependencies pass. 367 | select { 368 | case <-info.CancelCh: 369 | // Cancelled during an update while dependencies completed. 370 | return 371 | default: 372 | } 373 | 374 | // Run our callback or note that our upstream failed 375 | var err error 376 | var upstreamFailed bool 377 | if depsSuccess { 378 | err = w.Callback(v) 379 | } else { 380 | // TODO(@michaellzc): consider implementing otel tracing in the future 381 | // log.Printf("[TRACE] dag/walk: upstream of %q errored, so skipping", VertexName(v)) 382 | // This won't be displayed to the user because we'll set upstreamFailed, 383 | // but we need to ensure there's at least one error in here so that 384 | // the failures will cascade downstream. 385 | err = errors.New("upstream dependencies failed") 386 | upstreamFailed = true 387 | } 388 | 389 | // Record the result (we must do this after execution because we mustn't 390 | // hold diagsLock while visiting a vertex.) 391 | w.diagsLock.Lock() 392 | if w.errorsMap == nil { 393 | w.errorsMap = make(map[Vertex]error) 394 | } 395 | w.errorsMap[v] = err 396 | if w.upstreamFailed == nil { 397 | w.upstreamFailed = make(map[Vertex]struct{}) 398 | } 399 | if upstreamFailed { 400 | w.upstreamFailed[v] = struct{}{} 401 | } 402 | w.diagsLock.Unlock() 403 | } 404 | 405 | func (w *Walker) waitDeps( 406 | v Vertex, 407 | deps map[Vertex]<-chan struct{}, 408 | doneCh chan<- bool, 409 | cancelCh <-chan struct{}) { 410 | 411 | // For each dependency given to us, wait for it to complete 412 | for _, depCh := range deps { 413 | DepSatisfied: 414 | for { 415 | select { 416 | case <-depCh: 417 | // Dependency satisfied! 418 | break DepSatisfied 419 | 420 | case <-cancelCh: 421 | // Wait cancelled. Note that we didn't satisfy dependencies 422 | // so that anything waiting on us also doesn't run. 423 | doneCh <- false 424 | return 425 | 426 | case <-time.After(time.Second * 5): 427 | // TODO(@michaellzc): consider implementing otel tracing in the future 428 | // log.Printf("[TRACE] dag/walk: vertex %q is waiting for %q", 429 | // VertexName(v), VertexName(dep)) 430 | } 431 | } 432 | } 433 | 434 | // Dependencies satisfied! We need to check if any errored 435 | w.diagsLock.Lock() 436 | defer w.diagsLock.Unlock() 437 | for dep := range deps { 438 | if w.errorsMap[dep] != nil { 439 | // One of our dependencies failed, so return false 440 | doneCh <- false 441 | return 442 | } 443 | } 444 | 445 | // All dependencies satisfied and successful 446 | doneCh <- true 447 | } 448 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 HashiCorp, Inc. 2 | 3 | Mozilla Public License, version 2.0 4 | 5 | 1. Definitions 6 | 7 | 1.1. “Contributor” 8 | 9 | means each individual or legal entity that creates, contributes to the 10 | creation of, or owns Covered Software. 11 | 12 | 1.2. “Contributor Version” 13 | 14 | means the combination of the Contributions of others (if any) used by a 15 | Contributor and that particular Contributor’s Contribution. 16 | 17 | 1.3. “Contribution” 18 | 19 | means Covered Software of a particular Contributor. 20 | 21 | 1.4. “Covered Software” 22 | 23 | means Source Code Form to which the initial Contributor has attached the 24 | notice in Exhibit A, the Executable Form of such Source Code Form, and 25 | Modifications of such Source Code Form, in each case including portions 26 | thereof. 27 | 28 | 1.5. “Incompatible With Secondary Licenses” 29 | means 30 | 31 | a. that the initial Contributor has attached the notice described in 32 | Exhibit B to the Covered Software; or 33 | 34 | b. that the Covered Software was made available under the terms of version 35 | 1.1 or earlier of the License, but not also under the terms of a 36 | Secondary License. 37 | 38 | 1.6. “Executable Form” 39 | 40 | means any form of the work other than Source Code Form. 41 | 42 | 1.7. “Larger Work” 43 | 44 | means a work that combines Covered Software with other material, in a separate 45 | file or files, that is not Covered Software. 46 | 47 | 1.8. “License” 48 | 49 | means this document. 50 | 51 | 1.9. “Licensable” 52 | 53 | means having the right to grant, to the maximum extent possible, whether at the 54 | time of the initial grant or subsequently, any and all of the rights conveyed by 55 | this License. 56 | 57 | 1.10. “Modifications” 58 | 59 | means any of the following: 60 | 61 | a. any file in Source Code Form that results from an addition to, deletion 62 | from, or modification of the contents of Covered Software; or 63 | 64 | b. any new file in Source Code Form that contains any Covered Software. 65 | 66 | 1.11. “Patent Claims” of a Contributor 67 | 68 | means any patent claim(s), including without limitation, method, process, 69 | and apparatus claims, in any patent Licensable by such Contributor that 70 | would be infringed, but for the grant of the License, by the making, 71 | using, selling, offering for sale, having made, import, or transfer of 72 | either its Contributions or its Contributor Version. 73 | 74 | 1.12. “Secondary License” 75 | 76 | means either the GNU General Public License, Version 2.0, the GNU Lesser 77 | General Public License, Version 2.1, the GNU Affero General Public 78 | License, Version 3.0, or any later versions of those licenses. 79 | 80 | 1.13. “Source Code Form” 81 | 82 | means the form of the work preferred for making modifications. 83 | 84 | 1.14. “You” (or “Your”) 85 | 86 | means an individual or a legal entity exercising rights under this 87 | License. For legal entities, “You” includes any entity that controls, is 88 | controlled by, or is under common control with You. For purposes of this 89 | definition, “control” means (a) the power, direct or indirect, to cause 90 | the direction or management of such entity, whether by contract or 91 | otherwise, or (b) ownership of more than fifty percent (50%) of the 92 | outstanding shares or beneficial ownership of such entity. 93 | 94 | 95 | 2. License Grants and Conditions 96 | 97 | 2.1. Grants 98 | 99 | Each Contributor hereby grants You a world-wide, royalty-free, 100 | non-exclusive license: 101 | 102 | a. under intellectual property rights (other than patent or trademark) 103 | Licensable by such Contributor to use, reproduce, make available, 104 | modify, display, perform, distribute, and otherwise exploit its 105 | Contributions, either on an unmodified basis, with Modifications, or as 106 | part of a Larger Work; and 107 | 108 | b. under Patent Claims of such Contributor to make, use, sell, offer for 109 | sale, have made, import, and otherwise transfer either its Contributions 110 | or its Contributor Version. 111 | 112 | 2.2. Effective Date 113 | 114 | The licenses granted in Section 2.1 with respect to any Contribution become 115 | effective for each Contribution on the date the Contributor first distributes 116 | such Contribution. 117 | 118 | 2.3. Limitations on Grant Scope 119 | 120 | The licenses granted in this Section 2 are the only rights granted under this 121 | License. No additional rights or licenses will be implied from the distribution 122 | or licensing of Covered Software under this License. Notwithstanding Section 123 | 2.1(b) above, no patent license is granted by a Contributor: 124 | 125 | a. for any code that a Contributor has removed from Covered Software; or 126 | 127 | b. for infringements caused by: (i) Your and any other third party’s 128 | modifications of Covered Software, or (ii) the combination of its 129 | Contributions with other software (except as part of its Contributor 130 | Version); or 131 | 132 | c. under Patent Claims infringed by Covered Software in the absence of its 133 | Contributions. 134 | 135 | This License does not grant any rights in the trademarks, service marks, or 136 | logos of any Contributor (except as may be necessary to comply with the 137 | notice requirements in Section 3.4). 138 | 139 | 2.4. Subsequent Licenses 140 | 141 | No Contributor makes additional grants as a result of Your choice to 142 | distribute the Covered Software under a subsequent version of this License 143 | (see Section 10.2) or under the terms of a Secondary License (if permitted 144 | under the terms of Section 3.3). 145 | 146 | 2.5. Representation 147 | 148 | Each Contributor represents that the Contributor believes its Contributions 149 | are its original creation(s) or it has sufficient rights to grant the 150 | rights to its Contributions conveyed by this License. 151 | 152 | 2.6. Fair Use 153 | 154 | This License is not intended to limit any rights You have under applicable 155 | copyright doctrines of fair use, fair dealing, or other equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under the 169 | terms of this License. You must inform recipients that the Source Code Form 170 | of the Covered Software is governed by the terms of this License, and how 171 | they can obtain a copy of this License. You may not attempt to alter or 172 | restrict the recipients’ rights in the Source Code Form. 173 | 174 | 3.2. Distribution of Executable Form 175 | 176 | If You distribute Covered Software in Executable Form then: 177 | 178 | a. such Covered Software must also be made available in Source Code Form, 179 | as described in Section 3.1, and You must inform recipients of the 180 | Executable Form how they can obtain a copy of such Source Code Form by 181 | reasonable means in a timely manner, at a charge no more than the cost 182 | of distribution to the recipient; and 183 | 184 | b. You may distribute such Executable Form under the terms of this License, 185 | or sublicense it under different terms, provided that the license for 186 | the Executable Form does not attempt to limit or alter the recipients’ 187 | rights in the Source Code Form under this License. 188 | 189 | 3.3. Distribution of a Larger Work 190 | 191 | You may create and distribute a Larger Work under terms of Your choice, 192 | provided that You also comply with the requirements of this License for the 193 | Covered Software. If the Larger Work is a combination of Covered Software 194 | with a work governed by one or more Secondary Licenses, and the Covered 195 | Software is not Incompatible With Secondary Licenses, this License permits 196 | You to additionally distribute such Covered Software under the terms of 197 | such Secondary License(s), so that the recipient of the Larger Work may, at 198 | their option, further distribute the Covered Software under the terms of 199 | either this License or such Secondary License(s). 200 | 201 | 3.4. Notices 202 | 203 | You may not remove or alter the substance of any license notices (including 204 | copyright notices, patent notices, disclaimers of warranty, or limitations 205 | of liability) contained within the Source Code Form of the Covered 206 | Software, except that You may alter any license notices to the extent 207 | required to remedy known factual inaccuracies. 208 | 209 | 3.5. Application of Additional Terms 210 | 211 | You may choose to offer, and to charge a fee for, warranty, support, 212 | indemnity or liability obligations to one or more recipients of Covered 213 | Software. However, You may do so only on Your own behalf, and not on behalf 214 | of any Contributor. You must make it absolutely clear that any such 215 | warranty, support, indemnity, or liability obligation is offered by You 216 | alone, and You hereby agree to indemnify every Contributor for any 217 | liability incurred by such Contributor as a result of warranty, support, 218 | indemnity or liability terms You offer. You may include additional 219 | disclaimers of warranty and limitations of liability specific to any 220 | jurisdiction. 221 | 222 | 4. Inability to Comply Due to Statute or Regulation 223 | 224 | If it is impossible for You to comply with any of the terms of this License 225 | with respect to some or all of the Covered Software due to statute, judicial 226 | order, or regulation then You must: (a) comply with the terms of this License 227 | to the maximum extent possible; and (b) describe the limitations and the code 228 | they affect. Such description must be placed in a text file included with all 229 | distributions of the Covered Software under this License. Except to the 230 | extent prohibited by statute or regulation, such description must be 231 | sufficiently detailed for a recipient of ordinary skill to be able to 232 | understand it. 233 | 234 | 5. Termination 235 | 236 | 5.1. The rights granted under this License will terminate automatically if You 237 | fail to comply with any of its terms. However, if You become compliant, 238 | then the rights granted under this License from a particular Contributor 239 | are reinstated (a) provisionally, unless and until such Contributor 240 | explicitly and finally terminates Your grants, and (b) on an ongoing basis, 241 | if such Contributor fails to notify You of the non-compliance by some 242 | reasonable means prior to 60 days after You have come back into compliance. 243 | Moreover, Your grants from a particular Contributor are reinstated on an 244 | ongoing basis if such Contributor notifies You of the non-compliance by 245 | some reasonable means, this is the first time You have received notice of 246 | non-compliance with this License from such Contributor, and You become 247 | compliant prior to 30 days after Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, counter-claims, 251 | and cross-claims) alleging that a Contributor Version directly or 252 | indirectly infringes any patent, then the rights granted to You by any and 253 | all Contributors for the Covered Software under Section 2.1 of this License 254 | shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 257 | license agreements (excluding distributors and resellers) which have been 258 | validly granted by You or Your distributors under this License prior to 259 | termination shall survive termination. 260 | 261 | 6. Disclaimer of Warranty 262 | 263 | Covered Software is provided under this License on an “as is” basis, without 264 | warranty of any kind, either expressed, implied, or statutory, including, 265 | without limitation, warranties that the Covered Software is free of defects, 266 | merchantable, fit for a particular purpose or non-infringing. The entire 267 | risk as to the quality and performance of the Covered Software is with You. 268 | Should any Covered Software prove defective in any respect, You (not any 269 | Contributor) assume the cost of any necessary servicing, repair, or 270 | correction. This disclaimer of warranty constitutes an essential part of this 271 | License. No use of any Covered Software is authorized under this License 272 | except under this disclaimer. 273 | 274 | 7. Limitation of Liability 275 | 276 | Under no circumstances and under no legal theory, whether tort (including 277 | negligence), contract, or otherwise, shall any Contributor, or anyone who 278 | distributes Covered Software as permitted above, be liable to You for any 279 | direct, indirect, special, incidental, or consequential damages of any 280 | character including, without limitation, damages for lost profits, loss of 281 | goodwill, work stoppage, computer failure or malfunction, or any and all 282 | other commercial damages or losses, even if such party shall have been 283 | informed of the possibility of such damages. This limitation of liability 284 | shall not apply to liability for death or personal injury resulting from such 285 | party’s negligence to the extent applicable law prohibits such limitation. 286 | Some jurisdictions do not allow the exclusion or limitation of incidental or 287 | consequential damages, so this exclusion and limitation may not apply to You. 288 | 289 | 8. Litigation 290 | 291 | Any litigation relating to this License may be brought only in the courts of 292 | a jurisdiction where the defendant maintains its principal place of business 293 | and such litigation shall be governed by laws of that jurisdiction, without 294 | reference to its conflict-of-law provisions. Nothing in this Section shall 295 | prevent a party’s ability to bring cross-claims or counter-claims. 296 | 297 | 9. Miscellaneous 298 | 299 | This License represents the complete agreement concerning the subject matter 300 | hereof. If any provision of this License is held to be unenforceable, such 301 | provision shall be reformed only to the extent necessary to make it 302 | enforceable. Any law or regulation which provides that the language of a 303 | contract shall be construed against the drafter shall not be used to construe 304 | this License against a Contributor. 305 | 306 | 307 | 10. Versions of the License 308 | 309 | 10.1. New Versions 310 | 311 | Mozilla Foundation is the license steward. Except as provided in Section 312 | 10.3, no one other than the license steward has the right to modify or 313 | publish new versions of this License. Each version will be given a 314 | distinguishing version number. 315 | 316 | 10.2. Effect of New Versions 317 | 318 | You may distribute the Covered Software under the terms of the version of 319 | the License under which You originally received the Covered Software, or 320 | under the terms of any subsequent version published by the license 321 | steward. 322 | 323 | 10.3. Modified Versions 324 | 325 | If you create software not governed by this License, and you want to 326 | create a new license for such software, you may create and use a modified 327 | version of this License if you rename the license and remove any 328 | references to the name of the license steward (except to note that such 329 | modified license differs from this License). 330 | 331 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 332 | If You choose to distribute Source Code Form that is Incompatible With 333 | Secondary Licenses under the terms of this version of the License, the 334 | notice described in Exhibit B of this License must be attached. 335 | 336 | Exhibit A - Source Code Form License Notice 337 | 338 | This Source Code Form is subject to the 339 | terms of the Mozilla Public License, v. 340 | 2.0. If a copy of the MPL was not 341 | distributed with this file, You can 342 | obtain one at 343 | http://mozilla.org/MPL/2.0/. 344 | 345 | If it is not possible or desirable to put the notice in a particular file, then 346 | You may include the notice in a location (such as a LICENSE file in a relevant 347 | directory) where a recipient would be likely to look for such a notice. 348 | 349 | You may add additional accurate notices of copyright ownership. 350 | 351 | Exhibit B - “Incompatible With Secondary Licenses” Notice 352 | 353 | This Source Code Form is “Incompatible 354 | With Secondary Licenses”, as defined by 355 | the Mozilla Public License, v. 2.0. 356 | --------------------------------------------------------------------------------