github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/dag/dag_test.go (about)

     1  package dag
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"os"
     7  	"reflect"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  	"testing"
    12  
    13  	"github.com/iaas-resource-provision/iaas-rpc/internal/tfdiags"
    14  
    15  	_ "github.com/iaas-resource-provision/iaas-rpc/internal/logging"
    16  )
    17  
    18  func TestMain(m *testing.M) {
    19  	flag.Parse()
    20  	os.Exit(m.Run())
    21  }
    22  
    23  func TestAcyclicGraphRoot(t *testing.T) {
    24  	var g AcyclicGraph
    25  	g.Add(1)
    26  	g.Add(2)
    27  	g.Add(3)
    28  	g.Connect(BasicEdge(3, 2))
    29  	g.Connect(BasicEdge(3, 1))
    30  
    31  	if root, err := g.Root(); err != nil {
    32  		t.Fatalf("err: %s", err)
    33  	} else if root != 3 {
    34  		t.Fatalf("bad: %#v", root)
    35  	}
    36  }
    37  
    38  func TestAcyclicGraphRoot_cycle(t *testing.T) {
    39  	var g AcyclicGraph
    40  	g.Add(1)
    41  	g.Add(2)
    42  	g.Add(3)
    43  	g.Connect(BasicEdge(1, 2))
    44  	g.Connect(BasicEdge(2, 3))
    45  	g.Connect(BasicEdge(3, 1))
    46  
    47  	if _, err := g.Root(); err == nil {
    48  		t.Fatal("should error")
    49  	}
    50  }
    51  
    52  func TestAcyclicGraphRoot_multiple(t *testing.T) {
    53  	var g AcyclicGraph
    54  	g.Add(1)
    55  	g.Add(2)
    56  	g.Add(3)
    57  	g.Connect(BasicEdge(3, 2))
    58  
    59  	if _, err := g.Root(); err == nil {
    60  		t.Fatal("should error")
    61  	}
    62  }
    63  
    64  func TestAyclicGraphTransReduction(t *testing.T) {
    65  	var g AcyclicGraph
    66  	g.Add(1)
    67  	g.Add(2)
    68  	g.Add(3)
    69  	g.Connect(BasicEdge(1, 2))
    70  	g.Connect(BasicEdge(1, 3))
    71  	g.Connect(BasicEdge(2, 3))
    72  	g.TransitiveReduction()
    73  
    74  	actual := strings.TrimSpace(g.String())
    75  	expected := strings.TrimSpace(testGraphTransReductionStr)
    76  	if actual != expected {
    77  		t.Fatalf("bad: %s", actual)
    78  	}
    79  }
    80  
    81  func TestAyclicGraphTransReduction_more(t *testing.T) {
    82  	var g AcyclicGraph
    83  	g.Add(1)
    84  	g.Add(2)
    85  	g.Add(3)
    86  	g.Add(4)
    87  	g.Connect(BasicEdge(1, 2))
    88  	g.Connect(BasicEdge(1, 3))
    89  	g.Connect(BasicEdge(1, 4))
    90  	g.Connect(BasicEdge(2, 3))
    91  	g.Connect(BasicEdge(2, 4))
    92  	g.Connect(BasicEdge(3, 4))
    93  	g.TransitiveReduction()
    94  
    95  	actual := strings.TrimSpace(g.String())
    96  	expected := strings.TrimSpace(testGraphTransReductionMoreStr)
    97  	if actual != expected {
    98  		t.Fatalf("bad: %s", actual)
    99  	}
   100  }
   101  
   102  // use this to simulate slow sort operations
   103  type counter struct {
   104  	Name  string
   105  	Calls int64
   106  }
   107  
   108  func (s *counter) String() string {
   109  	s.Calls++
   110  	return s.Name
   111  }
   112  
   113  // Make sure we can reduce a sizable, fully-connected graph.
   114  func TestAyclicGraphTransReduction_fullyConnected(t *testing.T) {
   115  	var g AcyclicGraph
   116  
   117  	const nodeCount = 200
   118  	nodes := make([]*counter, nodeCount)
   119  	for i := 0; i < nodeCount; i++ {
   120  		nodes[i] = &counter{Name: strconv.Itoa(i)}
   121  	}
   122  
   123  	// Add them all to the graph
   124  	for _, n := range nodes {
   125  		g.Add(n)
   126  	}
   127  
   128  	// connect them all
   129  	for i := range nodes {
   130  		for j := range nodes {
   131  			if i == j {
   132  				continue
   133  			}
   134  			g.Connect(BasicEdge(nodes[i], nodes[j]))
   135  		}
   136  	}
   137  
   138  	g.TransitiveReduction()
   139  
   140  	vertexNameCalls := int64(0)
   141  	for _, n := range nodes {
   142  		vertexNameCalls += n.Calls
   143  	}
   144  
   145  	switch {
   146  	case vertexNameCalls > 2*nodeCount:
   147  		// Make calling it more the 2x per node fatal.
   148  		// If we were sorting this would give us roughly ln(n)(n^3) calls, or
   149  		// >59000000 calls for 200 vertices.
   150  		t.Fatalf("VertexName called %d times", vertexNameCalls)
   151  	case vertexNameCalls > 0:
   152  		// we don't expect any calls, but a change here isn't necessarily fatal
   153  		t.Logf("WARNING: VertexName called %d times", vertexNameCalls)
   154  	}
   155  }
   156  
   157  func TestAcyclicGraphValidate(t *testing.T) {
   158  	var g AcyclicGraph
   159  	g.Add(1)
   160  	g.Add(2)
   161  	g.Add(3)
   162  	g.Connect(BasicEdge(3, 2))
   163  	g.Connect(BasicEdge(3, 1))
   164  
   165  	if err := g.Validate(); err != nil {
   166  		t.Fatalf("err: %s", err)
   167  	}
   168  }
   169  
   170  func TestAcyclicGraphValidate_cycle(t *testing.T) {
   171  	var g AcyclicGraph
   172  	g.Add(1)
   173  	g.Add(2)
   174  	g.Add(3)
   175  	g.Connect(BasicEdge(3, 2))
   176  	g.Connect(BasicEdge(3, 1))
   177  	g.Connect(BasicEdge(1, 2))
   178  	g.Connect(BasicEdge(2, 1))
   179  
   180  	if err := g.Validate(); err == nil {
   181  		t.Fatal("should error")
   182  	}
   183  }
   184  
   185  func TestAcyclicGraphValidate_cycleSelf(t *testing.T) {
   186  	var g AcyclicGraph
   187  	g.Add(1)
   188  	g.Add(2)
   189  	g.Connect(BasicEdge(1, 1))
   190  
   191  	if err := g.Validate(); err == nil {
   192  		t.Fatal("should error")
   193  	}
   194  }
   195  
   196  func TestAcyclicGraphAncestors(t *testing.T) {
   197  	var g AcyclicGraph
   198  	g.Add(1)
   199  	g.Add(2)
   200  	g.Add(3)
   201  	g.Add(4)
   202  	g.Add(5)
   203  	g.Connect(BasicEdge(0, 1))
   204  	g.Connect(BasicEdge(1, 2))
   205  	g.Connect(BasicEdge(2, 3))
   206  	g.Connect(BasicEdge(3, 4))
   207  	g.Connect(BasicEdge(4, 5))
   208  
   209  	actual, err := g.Ancestors(2)
   210  	if err != nil {
   211  		t.Fatalf("err: %#v", err)
   212  	}
   213  
   214  	expected := []Vertex{3, 4, 5}
   215  
   216  	if actual.Len() != len(expected) {
   217  		t.Fatalf("bad length! expected %#v to have len %d", actual, len(expected))
   218  	}
   219  
   220  	for _, e := range expected {
   221  		if !actual.Include(e) {
   222  			t.Fatalf("expected: %#v to include: %#v", expected, actual)
   223  		}
   224  	}
   225  }
   226  
   227  func TestAcyclicGraphDescendents(t *testing.T) {
   228  	var g AcyclicGraph
   229  	g.Add(1)
   230  	g.Add(2)
   231  	g.Add(3)
   232  	g.Add(4)
   233  	g.Add(5)
   234  	g.Connect(BasicEdge(0, 1))
   235  	g.Connect(BasicEdge(1, 2))
   236  	g.Connect(BasicEdge(2, 3))
   237  	g.Connect(BasicEdge(3, 4))
   238  	g.Connect(BasicEdge(4, 5))
   239  
   240  	actual, err := g.Descendents(2)
   241  	if err != nil {
   242  		t.Fatalf("err: %#v", err)
   243  	}
   244  
   245  	expected := []Vertex{0, 1}
   246  
   247  	if actual.Len() != len(expected) {
   248  		t.Fatalf("bad length! expected %#v to have len %d", actual, len(expected))
   249  	}
   250  
   251  	for _, e := range expected {
   252  		if !actual.Include(e) {
   253  			t.Fatalf("expected: %#v to include: %#v", expected, actual)
   254  		}
   255  	}
   256  }
   257  
   258  func TestAcyclicGraphWalk(t *testing.T) {
   259  	var g AcyclicGraph
   260  	g.Add(1)
   261  	g.Add(2)
   262  	g.Add(3)
   263  	g.Connect(BasicEdge(3, 2))
   264  	g.Connect(BasicEdge(3, 1))
   265  
   266  	var visits []Vertex
   267  	var lock sync.Mutex
   268  	err := g.Walk(func(v Vertex) tfdiags.Diagnostics {
   269  		lock.Lock()
   270  		defer lock.Unlock()
   271  		visits = append(visits, v)
   272  		return nil
   273  	})
   274  	if err != nil {
   275  		t.Fatalf("err: %s", err)
   276  	}
   277  
   278  	expected := [][]Vertex{
   279  		{1, 2, 3},
   280  		{2, 1, 3},
   281  	}
   282  	for _, e := range expected {
   283  		if reflect.DeepEqual(visits, e) {
   284  			return
   285  		}
   286  	}
   287  
   288  	t.Fatalf("bad: %#v", visits)
   289  }
   290  
   291  func TestAcyclicGraphWalk_error(t *testing.T) {
   292  	var g AcyclicGraph
   293  	g.Add(1)
   294  	g.Add(2)
   295  	g.Add(3)
   296  	g.Add(4)
   297  	g.Connect(BasicEdge(4, 3))
   298  	g.Connect(BasicEdge(3, 2))
   299  	g.Connect(BasicEdge(2, 1))
   300  
   301  	var visits []Vertex
   302  	var lock sync.Mutex
   303  	err := g.Walk(func(v Vertex) tfdiags.Diagnostics {
   304  		lock.Lock()
   305  		defer lock.Unlock()
   306  
   307  		var diags tfdiags.Diagnostics
   308  
   309  		if v == 2 {
   310  			diags = diags.Append(fmt.Errorf("error"))
   311  			return diags
   312  		}
   313  
   314  		visits = append(visits, v)
   315  		return diags
   316  	})
   317  	if err == nil {
   318  		t.Fatal("should error")
   319  	}
   320  
   321  	expected := []Vertex{1}
   322  	if !reflect.DeepEqual(visits, expected) {
   323  		t.Errorf("wrong visits\ngot:  %#v\nwant: %#v", visits, expected)
   324  	}
   325  
   326  }
   327  
   328  func BenchmarkDAG(b *testing.B) {
   329  	for i := 0; i < b.N; i++ {
   330  		count := 150
   331  		b.StopTimer()
   332  		g := &AcyclicGraph{}
   333  
   334  		// create 4 layers of fully connected nodes
   335  		// layer A
   336  		for i := 0; i < count; i++ {
   337  			g.Add(fmt.Sprintf("A%d", i))
   338  		}
   339  
   340  		// layer B
   341  		for i := 0; i < count; i++ {
   342  			B := fmt.Sprintf("B%d", i)
   343  			g.Add(B)
   344  			for j := 0; j < count; j++ {
   345  				g.Connect(BasicEdge(B, fmt.Sprintf("A%d", j)))
   346  			}
   347  		}
   348  
   349  		// layer C
   350  		for i := 0; i < count; i++ {
   351  			c := fmt.Sprintf("C%d", i)
   352  			g.Add(c)
   353  			for j := 0; j < count; j++ {
   354  				// connect them to previous layers so we have something that requires reduction
   355  				g.Connect(BasicEdge(c, fmt.Sprintf("A%d", j)))
   356  				g.Connect(BasicEdge(c, fmt.Sprintf("B%d", j)))
   357  			}
   358  		}
   359  
   360  		// layer D
   361  		for i := 0; i < count; i++ {
   362  			d := fmt.Sprintf("D%d", i)
   363  			g.Add(d)
   364  			for j := 0; j < count; j++ {
   365  				g.Connect(BasicEdge(d, fmt.Sprintf("A%d", j)))
   366  				g.Connect(BasicEdge(d, fmt.Sprintf("B%d", j)))
   367  				g.Connect(BasicEdge(d, fmt.Sprintf("C%d", j)))
   368  			}
   369  		}
   370  
   371  		b.StartTimer()
   372  		// Find dependencies for every node
   373  		for _, v := range g.Vertices() {
   374  			_, err := g.Ancestors(v)
   375  			if err != nil {
   376  				b.Fatal(err)
   377  			}
   378  		}
   379  
   380  		// reduce the final graph
   381  		g.TransitiveReduction()
   382  	}
   383  }
   384  
   385  func TestAcyclicGraph_ReverseDepthFirstWalk_WithRemoval(t *testing.T) {
   386  	var g AcyclicGraph
   387  	g.Add(1)
   388  	g.Add(2)
   389  	g.Add(3)
   390  	g.Connect(BasicEdge(3, 2))
   391  	g.Connect(BasicEdge(2, 1))
   392  
   393  	var visits []Vertex
   394  	var lock sync.Mutex
   395  	err := g.SortedReverseDepthFirstWalk([]Vertex{1}, func(v Vertex, d int) error {
   396  		lock.Lock()
   397  		defer lock.Unlock()
   398  		visits = append(visits, v)
   399  		g.Remove(v)
   400  		return nil
   401  	})
   402  	if err != nil {
   403  		t.Fatalf("err: %s", err)
   404  	}
   405  
   406  	expected := []Vertex{1, 2, 3}
   407  	if !reflect.DeepEqual(visits, expected) {
   408  		t.Fatalf("expected: %#v, got: %#v", expected, visits)
   409  	}
   410  }
   411  
   412  const testGraphTransReductionStr = `
   413  1
   414    2
   415  2
   416    3
   417  3
   418  `
   419  
   420  const testGraphTransReductionMoreStr = `
   421  1
   422    2
   423  2
   424    3
   425  3
   426    4
   427  4
   428  `