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 }