github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/dag/dag_test.go (about)

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