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

     1  package dag
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    11  )
    12  
    13  func TestWalker_basic(t *testing.T) {
    14  	var g AcyclicGraph
    15  	g.Add(1)
    16  	g.Add(2)
    17  	g.Connect(BasicEdge(1, 2))
    18  
    19  	// Run it a bunch of times since it is timing dependent
    20  	for i := 0; i < 50; i++ {
    21  		var order []interface{}
    22  		w := &Walker{Callback: walkCbRecord(&order)}
    23  		w.Update(&g)
    24  
    25  		// Wait
    26  		if err := w.Wait(); err != nil {
    27  			t.Fatalf("err: %s", err)
    28  		}
    29  
    30  		// Check
    31  		expected := []interface{}{1, 2}
    32  		if !reflect.DeepEqual(order, expected) {
    33  			t.Errorf("wrong order\ngot:  %#v\nwant: %#v", order, expected)
    34  		}
    35  	}
    36  }
    37  
    38  func TestWalker_updateNilGraph(t *testing.T) {
    39  	var g AcyclicGraph
    40  	g.Add(1)
    41  	g.Add(2)
    42  	g.Connect(BasicEdge(1, 2))
    43  
    44  	// Run it a bunch of times since it is timing dependent
    45  	for i := 0; i < 50; i++ {
    46  		var order []interface{}
    47  		w := &Walker{Callback: walkCbRecord(&order)}
    48  		w.Update(&g)
    49  		w.Update(nil)
    50  
    51  		// Wait
    52  		if err := w.Wait(); err != nil {
    53  			t.Fatalf("err: %s", err)
    54  		}
    55  	}
    56  }
    57  
    58  func TestWalker_error(t *testing.T) {
    59  	var g AcyclicGraph
    60  	g.Add(1)
    61  	g.Add(2)
    62  	g.Add(3)
    63  	g.Add(4)
    64  	g.Connect(BasicEdge(1, 2))
    65  	g.Connect(BasicEdge(2, 3))
    66  	g.Connect(BasicEdge(3, 4))
    67  
    68  	// Record function
    69  	var order []interface{}
    70  	recordF := walkCbRecord(&order)
    71  
    72  	// Build a callback that delays until we close a channel
    73  	cb := func(v Vertex) tfdiags.Diagnostics {
    74  		if v == 2 {
    75  			var diags tfdiags.Diagnostics
    76  			diags = diags.Append(fmt.Errorf("error"))
    77  			return diags
    78  		}
    79  
    80  		return recordF(v)
    81  	}
    82  
    83  	w := &Walker{Callback: cb}
    84  	w.Update(&g)
    85  
    86  	// Wait
    87  	if err := w.Wait(); err == nil {
    88  		t.Fatal("expect error")
    89  	}
    90  
    91  	// Check
    92  	expected := []interface{}{1}
    93  	if !reflect.DeepEqual(order, expected) {
    94  		t.Errorf("wrong order\ngot:  %#v\nwant: %#v", order, expected)
    95  	}
    96  }
    97  
    98  func TestWalker_newVertex(t *testing.T) {
    99  	var g AcyclicGraph
   100  	g.Add(1)
   101  	g.Add(2)
   102  	g.Connect(BasicEdge(1, 2))
   103  
   104  	// Record function
   105  	var order []interface{}
   106  	recordF := walkCbRecord(&order)
   107  	done2 := make(chan int)
   108  
   109  	// Build a callback that notifies us when 2 has been walked
   110  	var w *Walker
   111  	cb := func(v Vertex) tfdiags.Diagnostics {
   112  		if v == 2 {
   113  			defer close(done2)
   114  		}
   115  		return recordF(v)
   116  	}
   117  
   118  	// Add the initial vertices
   119  	w = &Walker{Callback: cb}
   120  	w.Update(&g)
   121  
   122  	// if 2 has been visited, the walk is complete so far
   123  	<-done2
   124  
   125  	// Update the graph
   126  	g.Add(3)
   127  	w.Update(&g)
   128  
   129  	// Update the graph again but with the same vertex
   130  	g.Add(3)
   131  	w.Update(&g)
   132  
   133  	// Wait
   134  	if err := w.Wait(); err != nil {
   135  		t.Fatalf("err: %s", err)
   136  	}
   137  
   138  	// Check
   139  	expected := []interface{}{1, 2, 3}
   140  	if !reflect.DeepEqual(order, expected) {
   141  		t.Errorf("wrong order\ngot:  %#v\nwant: %#v", order, expected)
   142  	}
   143  }
   144  
   145  func TestWalker_removeVertex(t *testing.T) {
   146  	var g AcyclicGraph
   147  	g.Add(1)
   148  	g.Add(2)
   149  	g.Connect(BasicEdge(1, 2))
   150  
   151  	// Record function
   152  	var order []interface{}
   153  	recordF := walkCbRecord(&order)
   154  
   155  	var w *Walker
   156  	cb := func(v Vertex) tfdiags.Diagnostics {
   157  		if v == 1 {
   158  			g.Remove(2)
   159  			w.Update(&g)
   160  		}
   161  
   162  		return recordF(v)
   163  	}
   164  
   165  	// Add the initial vertices
   166  	w = &Walker{Callback: cb}
   167  	w.Update(&g)
   168  
   169  	// Wait
   170  	if err := w.Wait(); err != nil {
   171  		t.Fatalf("err: %s", err)
   172  	}
   173  
   174  	// Check
   175  	expected := []interface{}{1}
   176  	if !reflect.DeepEqual(order, expected) {
   177  		t.Errorf("wrong order\ngot:  %#v\nwant: %#v", order, expected)
   178  	}
   179  }
   180  
   181  func TestWalker_newEdge(t *testing.T) {
   182  	var g AcyclicGraph
   183  	g.Add(1)
   184  	g.Add(2)
   185  	g.Connect(BasicEdge(1, 2))
   186  
   187  	// Record function
   188  	var order []interface{}
   189  	recordF := walkCbRecord(&order)
   190  
   191  	var w *Walker
   192  	cb := func(v Vertex) tfdiags.Diagnostics {
   193  		// record where we are first, otherwise the Updated vertex may get
   194  		// walked before the first visit.
   195  		diags := recordF(v)
   196  
   197  		if v == 1 {
   198  			g.Add(3)
   199  			g.Connect(BasicEdge(3, 2))
   200  			w.Update(&g)
   201  		}
   202  		return diags
   203  	}
   204  
   205  	// Add the initial vertices
   206  	w = &Walker{Callback: cb}
   207  	w.Update(&g)
   208  
   209  	// Wait
   210  	if err := w.Wait(); err != nil {
   211  		t.Fatalf("err: %s", err)
   212  	}
   213  
   214  	// Check
   215  	expected := []interface{}{1, 3, 2}
   216  	if !reflect.DeepEqual(order, expected) {
   217  		t.Errorf("wrong order\ngot:  %#v\nwant: %#v", order, expected)
   218  	}
   219  }
   220  
   221  func TestWalker_removeEdge(t *testing.T) {
   222  	var g AcyclicGraph
   223  	g.Add(1)
   224  	g.Add(2)
   225  	g.Add(3)
   226  	g.Connect(BasicEdge(1, 2))
   227  	g.Connect(BasicEdge(1, 3))
   228  	g.Connect(BasicEdge(3, 2))
   229  
   230  	// Record function
   231  	var order []interface{}
   232  	recordF := walkCbRecord(&order)
   233  
   234  	// The way this works is that our original graph forces
   235  	// the order of 1 => 3 => 2. During the execution of 1, we
   236  	// remove the edge forcing 3 before 2. Then, during the execution
   237  	// of 3, we wait on a channel that is only closed by 2, implicitly
   238  	// forcing 2 before 3 via the callback (and not the graph). If
   239  	// 2 cannot execute before 3 (edge removal is non-functional), then
   240  	// this test will timeout.
   241  	var w *Walker
   242  	gateCh := make(chan struct{})
   243  	cb := func(v Vertex) tfdiags.Diagnostics {
   244  		t.Logf("visit vertex %#v", v)
   245  		switch v {
   246  		case 1:
   247  			g.RemoveEdge(BasicEdge(3, 2))
   248  			w.Update(&g)
   249  			t.Logf("removed edge from 3 to 2")
   250  
   251  		case 2:
   252  			// this visit isn't completed until we've recorded it
   253  			// Once the visit is official, we can then close the gate to
   254  			// let 3 continue.
   255  			defer close(gateCh)
   256  			defer t.Logf("2 unblocked 3")
   257  
   258  		case 3:
   259  			select {
   260  			case <-gateCh:
   261  				t.Logf("vertex 3 gate channel is now closed")
   262  			case <-time.After(500 * time.Millisecond):
   263  				t.Logf("vertex 3 timed out waiting for the gate channel to close")
   264  				var diags tfdiags.Diagnostics
   265  				diags = diags.Append(fmt.Errorf("timeout 3 waiting for 2"))
   266  				return diags
   267  			}
   268  		}
   269  
   270  		return recordF(v)
   271  	}
   272  
   273  	// Add the initial vertices
   274  	w = &Walker{Callback: cb}
   275  	w.Update(&g)
   276  
   277  	// Wait
   278  	if diags := w.Wait(); diags.HasErrors() {
   279  		t.Fatalf("unexpected errors: %s", diags.Err())
   280  	}
   281  
   282  	// Check
   283  	expected := []interface{}{1, 2, 3}
   284  	if !reflect.DeepEqual(order, expected) {
   285  		t.Errorf("wrong order\ngot:  %#v\nwant: %#v", order, expected)
   286  	}
   287  }
   288  
   289  // walkCbRecord is a test helper callback that just records the order called.
   290  func walkCbRecord(order *[]interface{}) WalkFunc {
   291  	var l sync.Mutex
   292  	return func(v Vertex) tfdiags.Diagnostics {
   293  		l.Lock()
   294  		defer l.Unlock()
   295  		*order = append(*order, v)
   296  		return nil
   297  	}
   298  }