2015-02-04 16:10:32 +01:00
|
|
|
package dag
|
|
|
|
|
|
|
|
import (
|
2016-08-01 23:16:22 +02:00
|
|
|
"flag"
|
2015-02-09 02:06:17 +01:00
|
|
|
"fmt"
|
2016-08-01 23:16:22 +02:00
|
|
|
"os"
|
2015-02-04 16:29:03 +01:00
|
|
|
"reflect"
|
2017-10-03 17:08:32 +02:00
|
|
|
"strconv"
|
2015-02-28 04:12:19 +01:00
|
|
|
"strings"
|
2015-02-04 16:29:03 +01:00
|
|
|
"sync"
|
2015-02-04 16:10:32 +01:00
|
|
|
"testing"
|
2016-08-01 23:16:22 +02:00
|
|
|
|
2018-04-07 01:25:35 +02:00
|
|
|
"github.com/hashicorp/terraform/tfdiags"
|
|
|
|
|
2020-10-18 16:01:48 +02:00
|
|
|
_ "github.com/hashicorp/terraform/internal/logging"
|
2015-02-04 16:10:32 +01:00
|
|
|
)
|
|
|
|
|
2016-08-01 23:16:22 +02:00
|
|
|
func TestMain(m *testing.M) {
|
|
|
|
flag.Parse()
|
|
|
|
os.Exit(m.Run())
|
|
|
|
}
|
|
|
|
|
2015-02-04 16:10:32 +01:00
|
|
|
func TestAcyclicGraphRoot(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
g.Add(1)
|
|
|
|
g.Add(2)
|
|
|
|
g.Add(3)
|
|
|
|
g.Connect(BasicEdge(3, 2))
|
|
|
|
g.Connect(BasicEdge(3, 1))
|
|
|
|
|
|
|
|
if root, err := g.Root(); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
} else if root != 3 {
|
|
|
|
t.Fatalf("bad: %#v", root)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAcyclicGraphRoot_cycle(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
g.Add(1)
|
|
|
|
g.Add(2)
|
|
|
|
g.Add(3)
|
|
|
|
g.Connect(BasicEdge(1, 2))
|
|
|
|
g.Connect(BasicEdge(2, 3))
|
|
|
|
g.Connect(BasicEdge(3, 1))
|
|
|
|
|
|
|
|
if _, err := g.Root(); err == nil {
|
|
|
|
t.Fatal("should error")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAcyclicGraphRoot_multiple(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
g.Add(1)
|
|
|
|
g.Add(2)
|
|
|
|
g.Add(3)
|
|
|
|
g.Connect(BasicEdge(3, 2))
|
|
|
|
|
|
|
|
if _, err := g.Root(); err == nil {
|
|
|
|
t.Fatal("should error")
|
|
|
|
}
|
|
|
|
}
|
2015-02-04 16:29:03 +01:00
|
|
|
|
2015-02-28 04:12:19 +01:00
|
|
|
func TestAyclicGraphTransReduction(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
g.Add(1)
|
|
|
|
g.Add(2)
|
|
|
|
g.Add(3)
|
|
|
|
g.Connect(BasicEdge(1, 2))
|
|
|
|
g.Connect(BasicEdge(1, 3))
|
|
|
|
g.Connect(BasicEdge(2, 3))
|
|
|
|
g.TransitiveReduction()
|
|
|
|
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
|
|
expected := strings.TrimSpace(testGraphTransReductionStr)
|
|
|
|
if actual != expected {
|
|
|
|
t.Fatalf("bad: %s", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAyclicGraphTransReduction_more(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
g.Add(1)
|
|
|
|
g.Add(2)
|
|
|
|
g.Add(3)
|
|
|
|
g.Add(4)
|
|
|
|
g.Connect(BasicEdge(1, 2))
|
|
|
|
g.Connect(BasicEdge(1, 3))
|
|
|
|
g.Connect(BasicEdge(1, 4))
|
|
|
|
g.Connect(BasicEdge(2, 3))
|
|
|
|
g.Connect(BasicEdge(2, 4))
|
|
|
|
g.Connect(BasicEdge(3, 4))
|
|
|
|
g.TransitiveReduction()
|
|
|
|
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
|
|
expected := strings.TrimSpace(testGraphTransReductionMoreStr)
|
|
|
|
if actual != expected {
|
|
|
|
t.Fatalf("bad: %s", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-03 17:08:32 +02:00
|
|
|
// use this to simulate slow sort operations
|
|
|
|
type counter struct {
|
|
|
|
Name string
|
|
|
|
Calls int64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *counter) String() string {
|
|
|
|
s.Calls++
|
|
|
|
return s.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure we can reduce a sizable, fully-connected graph.
|
|
|
|
func TestAyclicGraphTransReduction_fullyConnected(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
|
|
|
|
const nodeCount = 200
|
|
|
|
nodes := make([]*counter, nodeCount)
|
|
|
|
for i := 0; i < nodeCount; i++ {
|
|
|
|
nodes[i] = &counter{Name: strconv.Itoa(i)}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add them all to the graph
|
|
|
|
for _, n := range nodes {
|
|
|
|
g.Add(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
// connect them all
|
|
|
|
for i := range nodes {
|
|
|
|
for j := range nodes {
|
|
|
|
if i == j {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
g.Connect(BasicEdge(nodes[i], nodes[j]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g.TransitiveReduction()
|
|
|
|
|
|
|
|
vertexNameCalls := int64(0)
|
|
|
|
for _, n := range nodes {
|
|
|
|
vertexNameCalls += n.Calls
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case vertexNameCalls > 2*nodeCount:
|
|
|
|
// Make calling it more the 2x per node fatal.
|
|
|
|
// If we were sorting this would give us roughly ln(n)(n^3) calls, or
|
|
|
|
// >59000000 calls for 200 vertices.
|
|
|
|
t.Fatalf("VertexName called %d times", vertexNameCalls)
|
|
|
|
case vertexNameCalls > 0:
|
|
|
|
// we don't expect any calls, but a change here isn't necessarily fatal
|
|
|
|
t.Logf("WARNING: VertexName called %d times", vertexNameCalls)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-04 16:36:33 +01:00
|
|
|
func TestAcyclicGraphValidate(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
g.Add(1)
|
|
|
|
g.Add(2)
|
|
|
|
g.Add(3)
|
|
|
|
g.Connect(BasicEdge(3, 2))
|
|
|
|
g.Connect(BasicEdge(3, 1))
|
|
|
|
|
|
|
|
if err := g.Validate(); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAcyclicGraphValidate_cycle(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
g.Add(1)
|
|
|
|
g.Add(2)
|
|
|
|
g.Add(3)
|
|
|
|
g.Connect(BasicEdge(3, 2))
|
|
|
|
g.Connect(BasicEdge(3, 1))
|
|
|
|
g.Connect(BasicEdge(1, 2))
|
|
|
|
g.Connect(BasicEdge(2, 1))
|
|
|
|
|
|
|
|
if err := g.Validate(); err == nil {
|
|
|
|
t.Fatal("should error")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-07 18:52:34 +01:00
|
|
|
func TestAcyclicGraphValidate_cycleSelf(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
g.Add(1)
|
|
|
|
g.Add(2)
|
|
|
|
g.Connect(BasicEdge(1, 1))
|
|
|
|
|
|
|
|
if err := g.Validate(); err == nil {
|
|
|
|
t.Fatal("should error")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-24 17:18:15 +01:00
|
|
|
func TestAcyclicGraphAncestors(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
g.Add(1)
|
|
|
|
g.Add(2)
|
|
|
|
g.Add(3)
|
|
|
|
g.Add(4)
|
|
|
|
g.Add(5)
|
|
|
|
g.Connect(BasicEdge(0, 1))
|
|
|
|
g.Connect(BasicEdge(1, 2))
|
|
|
|
g.Connect(BasicEdge(2, 3))
|
|
|
|
g.Connect(BasicEdge(3, 4))
|
|
|
|
g.Connect(BasicEdge(4, 5))
|
|
|
|
|
|
|
|
actual, err := g.Ancestors(2)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %#v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := []Vertex{3, 4, 5}
|
|
|
|
|
|
|
|
if actual.Len() != len(expected) {
|
|
|
|
t.Fatalf("bad length! expected %#v to have len %d", actual, len(expected))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, e := range expected {
|
|
|
|
if !actual.Include(e) {
|
|
|
|
t.Fatalf("expected: %#v to include: %#v", expected, actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAcyclicGraphDescendents(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
g.Add(1)
|
|
|
|
g.Add(2)
|
|
|
|
g.Add(3)
|
|
|
|
g.Add(4)
|
|
|
|
g.Add(5)
|
|
|
|
g.Connect(BasicEdge(0, 1))
|
|
|
|
g.Connect(BasicEdge(1, 2))
|
|
|
|
g.Connect(BasicEdge(2, 3))
|
|
|
|
g.Connect(BasicEdge(3, 4))
|
|
|
|
g.Connect(BasicEdge(4, 5))
|
|
|
|
|
|
|
|
actual, err := g.Descendents(2)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %#v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := []Vertex{0, 1}
|
|
|
|
|
|
|
|
if actual.Len() != len(expected) {
|
|
|
|
t.Fatalf("bad length! expected %#v to have len %d", actual, len(expected))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, e := range expected {
|
|
|
|
if !actual.Include(e) {
|
|
|
|
t.Fatalf("expected: %#v to include: %#v", expected, actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-04 16:29:03 +01:00
|
|
|
func TestAcyclicGraphWalk(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
g.Add(1)
|
|
|
|
g.Add(2)
|
|
|
|
g.Add(3)
|
|
|
|
g.Connect(BasicEdge(3, 2))
|
|
|
|
g.Connect(BasicEdge(3, 1))
|
|
|
|
|
|
|
|
var visits []Vertex
|
|
|
|
var lock sync.Mutex
|
2018-04-07 01:25:35 +02:00
|
|
|
err := g.Walk(func(v Vertex) tfdiags.Diagnostics {
|
2015-02-04 16:29:03 +01:00
|
|
|
lock.Lock()
|
|
|
|
defer lock.Unlock()
|
|
|
|
visits = append(visits, v)
|
2015-02-09 02:06:17 +01:00
|
|
|
return nil
|
2015-02-04 16:29:03 +01:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := [][]Vertex{
|
2015-02-05 01:38:38 +01:00
|
|
|
{1, 2, 3},
|
|
|
|
{2, 1, 3},
|
2015-02-04 16:29:03 +01:00
|
|
|
}
|
|
|
|
for _, e := range expected {
|
|
|
|
if reflect.DeepEqual(visits, e) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Fatalf("bad: %#v", visits)
|
|
|
|
}
|
2015-02-09 02:06:17 +01:00
|
|
|
|
|
|
|
func TestAcyclicGraphWalk_error(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
g.Add(1)
|
|
|
|
g.Add(2)
|
|
|
|
g.Add(3)
|
2015-08-07 18:20:13 +02:00
|
|
|
g.Add(4)
|
|
|
|
g.Connect(BasicEdge(4, 3))
|
2015-02-09 02:06:17 +01:00
|
|
|
g.Connect(BasicEdge(3, 2))
|
2015-08-07 18:20:13 +02:00
|
|
|
g.Connect(BasicEdge(2, 1))
|
2015-02-09 02:06:17 +01:00
|
|
|
|
|
|
|
var visits []Vertex
|
|
|
|
var lock sync.Mutex
|
2018-04-07 01:25:35 +02:00
|
|
|
err := g.Walk(func(v Vertex) tfdiags.Diagnostics {
|
2015-02-09 02:06:17 +01:00
|
|
|
lock.Lock()
|
|
|
|
defer lock.Unlock()
|
|
|
|
|
2018-04-07 01:25:35 +02:00
|
|
|
var diags tfdiags.Diagnostics
|
|
|
|
|
2015-02-09 02:06:17 +01:00
|
|
|
if v == 2 {
|
2018-04-07 01:25:35 +02:00
|
|
|
diags = diags.Append(fmt.Errorf("error"))
|
|
|
|
return diags
|
2015-02-09 02:06:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
visits = append(visits, v)
|
2018-04-07 01:25:35 +02:00
|
|
|
return diags
|
2015-02-09 02:06:17 +01:00
|
|
|
})
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("should error")
|
|
|
|
}
|
|
|
|
|
2018-04-07 01:25:35 +02:00
|
|
|
expected := []Vertex{1}
|
|
|
|
if !reflect.DeepEqual(visits, expected) {
|
|
|
|
t.Errorf("wrong visits\ngot: %#v\nwant: %#v", visits, expected)
|
2015-02-09 02:06:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2015-02-28 04:12:19 +01:00
|
|
|
|
2020-01-08 19:56:49 +01:00
|
|
|
func BenchmarkDAG(b *testing.B) {
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
count := 150
|
|
|
|
b.StopTimer()
|
|
|
|
g := &AcyclicGraph{}
|
|
|
|
|
|
|
|
// create 4 layers of fully connected nodes
|
|
|
|
// layer A
|
|
|
|
for i := 0; i < count; i++ {
|
|
|
|
g.Add(fmt.Sprintf("A%d", i))
|
|
|
|
}
|
|
|
|
|
|
|
|
// layer B
|
|
|
|
for i := 0; i < count; i++ {
|
|
|
|
B := fmt.Sprintf("B%d", i)
|
|
|
|
g.Add(fmt.Sprintf(B))
|
|
|
|
for j := 0; j < count; j++ {
|
|
|
|
g.Connect(BasicEdge(B, fmt.Sprintf("A%d", j)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// layer C
|
|
|
|
for i := 0; i < count; i++ {
|
|
|
|
c := fmt.Sprintf("C%d", i)
|
|
|
|
g.Add(fmt.Sprintf(c))
|
|
|
|
for j := 0; j < count; j++ {
|
|
|
|
// connect them to previous layers so we have something that requires reduction
|
|
|
|
g.Connect(BasicEdge(c, fmt.Sprintf("A%d", j)))
|
|
|
|
g.Connect(BasicEdge(c, fmt.Sprintf("B%d", j)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// layer D
|
|
|
|
for i := 0; i < count; i++ {
|
|
|
|
d := fmt.Sprintf("D%d", i)
|
|
|
|
g.Add(fmt.Sprintf(d))
|
|
|
|
for j := 0; j < count; j++ {
|
|
|
|
g.Connect(BasicEdge(d, fmt.Sprintf("A%d", j)))
|
|
|
|
g.Connect(BasicEdge(d, fmt.Sprintf("B%d", j)))
|
|
|
|
g.Connect(BasicEdge(d, fmt.Sprintf("C%d", j)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
b.StartTimer()
|
|
|
|
// Find dependencies for every node
|
|
|
|
for _, v := range g.Vertices() {
|
|
|
|
_, err := g.Ancestors(v)
|
|
|
|
if err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// reduce the final graph
|
|
|
|
g.TransitiveReduction()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-14 17:33:37 +02:00
|
|
|
func TestAcyclicGraph_ReverseDepthFirstWalk_WithRemoval(t *testing.T) {
|
|
|
|
var g AcyclicGraph
|
|
|
|
g.Add(1)
|
|
|
|
g.Add(2)
|
|
|
|
g.Add(3)
|
|
|
|
g.Connect(BasicEdge(3, 2))
|
|
|
|
g.Connect(BasicEdge(2, 1))
|
|
|
|
|
|
|
|
var visits []Vertex
|
|
|
|
var lock sync.Mutex
|
2020-01-07 23:28:56 +01:00
|
|
|
err := g.SortedReverseDepthFirstWalk([]Vertex{1}, func(v Vertex, d int) error {
|
2016-07-14 17:33:37 +02:00
|
|
|
lock.Lock()
|
|
|
|
defer lock.Unlock()
|
|
|
|
visits = append(visits, v)
|
|
|
|
g.Remove(v)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := []Vertex{1, 2, 3}
|
|
|
|
if !reflect.DeepEqual(visits, expected) {
|
|
|
|
t.Fatalf("expected: %#v, got: %#v", expected, visits)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-28 04:12:19 +01:00
|
|
|
const testGraphTransReductionStr = `
|
|
|
|
1
|
|
|
|
2
|
|
|
|
2
|
|
|
|
3
|
|
|
|
3
|
|
|
|
`
|
|
|
|
|
|
|
|
const testGraphTransReductionMoreStr = `
|
|
|
|
1
|
|
|
|
2
|
|
|
|
2
|
|
|
|
3
|
|
|
|
3
|
|
|
|
4
|
|
|
|
4
|
|
|
|
`
|