mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
05caff2ca3
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
299 lines
6.0 KiB
Go
299 lines
6.0 KiB
Go
package dag
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
func TestWalker_basic(t *testing.T) {
|
|
var g AcyclicGraph
|
|
g.Add(1)
|
|
g.Add(2)
|
|
g.Connect(BasicEdge(1, 2))
|
|
|
|
// Run it a bunch of times since it is timing dependent
|
|
for i := 0; i < 50; i++ {
|
|
var order []interface{}
|
|
w := &Walker{Callback: walkCbRecord(&order)}
|
|
w.Update(&g)
|
|
|
|
// Wait
|
|
if err := w.Wait(); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Check
|
|
expected := []interface{}{1, 2}
|
|
if !reflect.DeepEqual(order, expected) {
|
|
t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWalker_updateNilGraph(t *testing.T) {
|
|
var g AcyclicGraph
|
|
g.Add(1)
|
|
g.Add(2)
|
|
g.Connect(BasicEdge(1, 2))
|
|
|
|
// Run it a bunch of times since it is timing dependent
|
|
for i := 0; i < 50; i++ {
|
|
var order []interface{}
|
|
w := &Walker{Callback: walkCbRecord(&order)}
|
|
w.Update(&g)
|
|
w.Update(nil)
|
|
|
|
// Wait
|
|
if err := w.Wait(); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWalker_error(t *testing.T) {
|
|
var g AcyclicGraph
|
|
g.Add(1)
|
|
g.Add(2)
|
|
g.Add(3)
|
|
g.Add(4)
|
|
g.Connect(BasicEdge(1, 2))
|
|
g.Connect(BasicEdge(2, 3))
|
|
g.Connect(BasicEdge(3, 4))
|
|
|
|
// Record function
|
|
var order []interface{}
|
|
recordF := walkCbRecord(&order)
|
|
|
|
// Build a callback that delays until we close a channel
|
|
cb := func(v Vertex) tfdiags.Diagnostics {
|
|
if v == 2 {
|
|
var diags tfdiags.Diagnostics
|
|
diags = diags.Append(fmt.Errorf("error"))
|
|
return diags
|
|
}
|
|
|
|
return recordF(v)
|
|
}
|
|
|
|
w := &Walker{Callback: cb}
|
|
w.Update(&g)
|
|
|
|
// Wait
|
|
if err := w.Wait(); err == nil {
|
|
t.Fatal("expect error")
|
|
}
|
|
|
|
// Check
|
|
expected := []interface{}{1}
|
|
if !reflect.DeepEqual(order, expected) {
|
|
t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected)
|
|
}
|
|
}
|
|
|
|
func TestWalker_newVertex(t *testing.T) {
|
|
var g AcyclicGraph
|
|
g.Add(1)
|
|
g.Add(2)
|
|
g.Connect(BasicEdge(1, 2))
|
|
|
|
// Record function
|
|
var order []interface{}
|
|
recordF := walkCbRecord(&order)
|
|
done2 := make(chan int)
|
|
|
|
// Build a callback that notifies us when 2 has been walked
|
|
var w *Walker
|
|
cb := func(v Vertex) tfdiags.Diagnostics {
|
|
if v == 2 {
|
|
defer close(done2)
|
|
}
|
|
return recordF(v)
|
|
}
|
|
|
|
// Add the initial vertices
|
|
w = &Walker{Callback: cb}
|
|
w.Update(&g)
|
|
|
|
// if 2 has been visited, the walk is complete so far
|
|
<-done2
|
|
|
|
// Update the graph
|
|
g.Add(3)
|
|
w.Update(&g)
|
|
|
|
// Update the graph again but with the same vertex
|
|
g.Add(3)
|
|
w.Update(&g)
|
|
|
|
// Wait
|
|
if err := w.Wait(); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Check
|
|
expected := []interface{}{1, 2, 3}
|
|
if !reflect.DeepEqual(order, expected) {
|
|
t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected)
|
|
}
|
|
}
|
|
|
|
func TestWalker_removeVertex(t *testing.T) {
|
|
var g AcyclicGraph
|
|
g.Add(1)
|
|
g.Add(2)
|
|
g.Connect(BasicEdge(1, 2))
|
|
|
|
// Record function
|
|
var order []interface{}
|
|
recordF := walkCbRecord(&order)
|
|
|
|
var w *Walker
|
|
cb := func(v Vertex) tfdiags.Diagnostics {
|
|
if v == 1 {
|
|
g.Remove(2)
|
|
w.Update(&g)
|
|
}
|
|
|
|
return recordF(v)
|
|
}
|
|
|
|
// Add the initial vertices
|
|
w = &Walker{Callback: cb}
|
|
w.Update(&g)
|
|
|
|
// Wait
|
|
if err := w.Wait(); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Check
|
|
expected := []interface{}{1}
|
|
if !reflect.DeepEqual(order, expected) {
|
|
t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected)
|
|
}
|
|
}
|
|
|
|
func TestWalker_newEdge(t *testing.T) {
|
|
var g AcyclicGraph
|
|
g.Add(1)
|
|
g.Add(2)
|
|
g.Connect(BasicEdge(1, 2))
|
|
|
|
// Record function
|
|
var order []interface{}
|
|
recordF := walkCbRecord(&order)
|
|
|
|
var w *Walker
|
|
cb := func(v Vertex) tfdiags.Diagnostics {
|
|
// record where we are first, otherwise the Updated vertex may get
|
|
// walked before the first visit.
|
|
diags := recordF(v)
|
|
|
|
if v == 1 {
|
|
g.Add(3)
|
|
g.Connect(BasicEdge(3, 2))
|
|
w.Update(&g)
|
|
}
|
|
return diags
|
|
}
|
|
|
|
// Add the initial vertices
|
|
w = &Walker{Callback: cb}
|
|
w.Update(&g)
|
|
|
|
// Wait
|
|
if err := w.Wait(); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Check
|
|
expected := []interface{}{1, 3, 2}
|
|
if !reflect.DeepEqual(order, expected) {
|
|
t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected)
|
|
}
|
|
}
|
|
|
|
func TestWalker_removeEdge(t *testing.T) {
|
|
var g AcyclicGraph
|
|
g.Add(1)
|
|
g.Add(2)
|
|
g.Add(3)
|
|
g.Connect(BasicEdge(1, 2))
|
|
g.Connect(BasicEdge(1, 3))
|
|
g.Connect(BasicEdge(3, 2))
|
|
|
|
// Record function
|
|
var order []interface{}
|
|
recordF := walkCbRecord(&order)
|
|
|
|
// The way this works is that our original graph forces
|
|
// the order of 1 => 3 => 2. During the execution of 1, we
|
|
// remove the edge forcing 3 before 2. Then, during the execution
|
|
// of 3, we wait on a channel that is only closed by 2, implicitly
|
|
// forcing 2 before 3 via the callback (and not the graph). If
|
|
// 2 cannot execute before 3 (edge removal is non-functional), then
|
|
// this test will timeout.
|
|
var w *Walker
|
|
gateCh := make(chan struct{})
|
|
cb := func(v Vertex) tfdiags.Diagnostics {
|
|
t.Logf("visit vertex %#v", v)
|
|
switch v {
|
|
case 1:
|
|
g.RemoveEdge(BasicEdge(3, 2))
|
|
w.Update(&g)
|
|
t.Logf("removed edge from 3 to 2")
|
|
|
|
case 2:
|
|
// this visit isn't completed until we've recorded it
|
|
// Once the visit is official, we can then close the gate to
|
|
// let 3 continue.
|
|
defer close(gateCh)
|
|
defer t.Logf("2 unblocked 3")
|
|
|
|
case 3:
|
|
select {
|
|
case <-gateCh:
|
|
t.Logf("vertex 3 gate channel is now closed")
|
|
case <-time.After(500 * time.Millisecond):
|
|
t.Logf("vertex 3 timed out waiting for the gate channel to close")
|
|
var diags tfdiags.Diagnostics
|
|
diags = diags.Append(fmt.Errorf("timeout 3 waiting for 2"))
|
|
return diags
|
|
}
|
|
}
|
|
|
|
return recordF(v)
|
|
}
|
|
|
|
// Add the initial vertices
|
|
w = &Walker{Callback: cb}
|
|
w.Update(&g)
|
|
|
|
// Wait
|
|
if diags := w.Wait(); diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
// Check
|
|
expected := []interface{}{1, 2, 3}
|
|
if !reflect.DeepEqual(order, expected) {
|
|
t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected)
|
|
}
|
|
}
|
|
|
|
// walkCbRecord is a test helper callback that just records the order called.
|
|
func walkCbRecord(order *[]interface{}) WalkFunc {
|
|
var l sync.Mutex
|
|
return func(v Vertex) tfdiags.Diagnostics {
|
|
l.Lock()
|
|
defer l.Unlock()
|
|
*order = append(*order, v)
|
|
return nil
|
|
}
|
|
}
|