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 }