github.com/opentofu/opentofu@v1.7.1/internal/dag/walk_test.go (about)

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