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

     1  package dag
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    10  )
    11  
    12  func TestGraphDot_empty(t *testing.T) {
    13  	var g Graph
    14  	g.Add(1)
    15  	g.Add(2)
    16  	g.Add(3)
    17  
    18  	actual := strings.TrimSpace(string(g.Dot(nil)))
    19  	expected := strings.TrimSpace(testGraphDotEmptyStr)
    20  	if actual != expected {
    21  		t.Fatalf("bad: %s", actual)
    22  	}
    23  }
    24  
    25  func TestGraphDot_basic(t *testing.T) {
    26  	var g Graph
    27  	g.Add(1)
    28  	g.Add(2)
    29  	g.Add(3)
    30  	g.Connect(BasicEdge(1, 3))
    31  
    32  	actual := strings.TrimSpace(string(g.Dot(nil)))
    33  	expected := strings.TrimSpace(testGraphDotBasicStr)
    34  	if actual != expected {
    35  		t.Fatalf("bad: %s", actual)
    36  	}
    37  }
    38  
    39  func TestGraphDot_attrs(t *testing.T) {
    40  	var g Graph
    41  	g.Add(&testGraphNodeDotter{
    42  		Result: &DotNode{
    43  			Name:  "foo",
    44  			Attrs: map[string]string{"foo": "bar"},
    45  		},
    46  	})
    47  
    48  	actual := strings.TrimSpace(string(g.Dot(nil)))
    49  	expected := strings.TrimSpace(testGraphDotAttrsStr)
    50  	if actual != expected {
    51  		t.Fatalf("bad: %s", actual)
    52  	}
    53  }
    54  
    55  type testGraphNodeDotter struct{ Result *DotNode }
    56  
    57  func (n *testGraphNodeDotter) Name() string                      { return n.Result.Name }
    58  func (n *testGraphNodeDotter) DotNode(string, *DotOpts) *DotNode { return n.Result }
    59  
    60  const testGraphDotBasicStr = `digraph {
    61  	compound = "true"
    62  	newrank = "true"
    63  	subgraph "root" {
    64  		"[root] 1" -> "[root] 3"
    65  	}
    66  }
    67  `
    68  
    69  const testGraphDotEmptyStr = `digraph {
    70  	compound = "true"
    71  	newrank = "true"
    72  	subgraph "root" {
    73  	}
    74  }`
    75  
    76  const testGraphDotAttrsStr = `digraph {
    77  	compound = "true"
    78  	newrank = "true"
    79  	subgraph "root" {
    80  		"[root] foo" [foo = "bar"]
    81  	}
    82  }`
    83  
    84  func TestGraphJSON_empty(t *testing.T) {
    85  	var g Graph
    86  	g.Add(1)
    87  	g.Add(2)
    88  	g.Add(3)
    89  
    90  	js, err := g.MarshalJSON()
    91  	if err != nil {
    92  		t.Fatal(err)
    93  	}
    94  
    95  	actual := strings.TrimSpace(string(js))
    96  	expected := strings.TrimSpace(testGraphJSONEmptyStr)
    97  	if actual != expected {
    98  		t.Fatalf("bad: %s", actual)
    99  	}
   100  }
   101  
   102  func TestGraphJSON_basic(t *testing.T) {
   103  	var g Graph
   104  	g.Add(1)
   105  	g.Add(2)
   106  	g.Add(3)
   107  	g.Connect(BasicEdge(1, 3))
   108  
   109  	js, err := g.MarshalJSON()
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  	actual := strings.TrimSpace(string(js))
   114  	expected := strings.TrimSpace(testGraphJSONBasicStr)
   115  	if actual != expected {
   116  		t.Fatalf("bad: %s", actual)
   117  	}
   118  }
   119  
   120  // record some graph transformations, and make sure we get the same graph when
   121  // they're replayed
   122  func TestGraphJSON_basicRecord(t *testing.T) {
   123  	var g Graph
   124  	var buf bytes.Buffer
   125  	g.SetDebugWriter(&buf)
   126  
   127  	g.Add(1)
   128  	g.Add(2)
   129  	g.Add(3)
   130  	g.Connect(BasicEdge(1, 2))
   131  	g.Connect(BasicEdge(1, 3))
   132  	g.Connect(BasicEdge(2, 3))
   133  	(&AcyclicGraph{g}).TransitiveReduction()
   134  
   135  	recorded := buf.Bytes()
   136  	// the Walk doesn't happen in a determined order, so just count operations
   137  	// for now to make sure we wrote stuff out.
   138  	if len(bytes.Split(recorded, []byte{'\n'})) != 17 {
   139  		t.Fatalf("bad: %s", recorded)
   140  	}
   141  
   142  	original, err := g.MarshalJSON()
   143  	if err != nil {
   144  		t.Fatal(err)
   145  	}
   146  
   147  	// replay the logs, and marshal the graph back out again
   148  	m, err := decodeGraph(bytes.NewReader(buf.Bytes()))
   149  	if err != nil {
   150  		t.Fatal(err)
   151  	}
   152  
   153  	replayed, err := json.MarshalIndent(m, "", "  ")
   154  	if err != nil {
   155  		t.Fatal(err)
   156  	}
   157  
   158  	if !bytes.Equal(original, replayed) {
   159  		t.Fatalf("\noriginal: %s\nreplayed: %s", original, replayed)
   160  	}
   161  }
   162  
   163  // Verify that Vertex and Edge annotations appear in the debug output
   164  func TestGraphJSON_debugInfo(t *testing.T) {
   165  	var g Graph
   166  	var buf bytes.Buffer
   167  	g.SetDebugWriter(&buf)
   168  
   169  	g.Add(1)
   170  	g.Add(2)
   171  	g.Add(3)
   172  	g.Connect(BasicEdge(1, 2))
   173  
   174  	g.DebugVertexInfo(2, "2")
   175  	g.DebugVertexInfo(3, "3")
   176  	g.DebugEdgeInfo(BasicEdge(1, 2), "1|2")
   177  
   178  	dec := json.NewDecoder(bytes.NewReader(buf.Bytes()))
   179  
   180  	var found2, found3, foundEdge bool
   181  	for dec.More() {
   182  		var d streamDecode
   183  
   184  		err := dec.Decode(&d)
   185  		if err != nil {
   186  			t.Fatal(err)
   187  		}
   188  
   189  		switch d.Type {
   190  		case typeVertexInfo:
   191  			va := &marshalVertexInfo{}
   192  			err := json.Unmarshal(d.JSON, va)
   193  			if err != nil {
   194  				t.Fatal(err)
   195  			}
   196  
   197  			switch va.Info {
   198  			case "2":
   199  				if va.Vertex.Name != "2" {
   200  					t.Fatalf("wrong vertex annotated 2: %#v", va)
   201  				}
   202  				found2 = true
   203  			case "3":
   204  				if va.Vertex.Name != "3" {
   205  					t.Fatalf("wrong vertex annotated 3: %#v", va)
   206  				}
   207  				found3 = true
   208  			default:
   209  				t.Fatalf("unexpected annotation: %#v", va)
   210  			}
   211  		case typeEdgeInfo:
   212  			ea := &marshalEdgeInfo{}
   213  			err := json.Unmarshal(d.JSON, ea)
   214  			if err != nil {
   215  				t.Fatal(err)
   216  			}
   217  
   218  			switch ea.Info {
   219  			case "1|2":
   220  				if ea.Edge.Name != "1|2" {
   221  					t.Fatalf("incorrect edge annotation: %#v\n", ea)
   222  				}
   223  				foundEdge = true
   224  			default:
   225  				t.Fatalf("unexpected edge Info: %#v", ea)
   226  			}
   227  		}
   228  	}
   229  
   230  	if !found2 {
   231  		t.Fatal("annotation 2 not found")
   232  	}
   233  	if !found3 {
   234  		t.Fatal("annotation 3 not found")
   235  	}
   236  	if !foundEdge {
   237  		t.Fatal("edge annotation not found")
   238  	}
   239  }
   240  
   241  // Verify that debug operations appear in the debug output
   242  func TestGraphJSON_debugOperations(t *testing.T) {
   243  	var g Graph
   244  	var buf bytes.Buffer
   245  	g.SetDebugWriter(&buf)
   246  
   247  	debugOp := g.DebugOperation("AddOne", "adding node 1")
   248  	g.Add(1)
   249  	debugOp.End("done adding node 1")
   250  
   251  	// use an immediate closure to test defers
   252  	func() {
   253  		defer g.DebugOperation("AddTwo", "adding nodes 2 and 3").End("done adding 2 and 3")
   254  		g.Add(2)
   255  		defer g.DebugOperation("NestedAddThree", "second defer").End("done adding node 3")
   256  		g.Add(3)
   257  	}()
   258  
   259  	g.Connect(BasicEdge(1, 2))
   260  
   261  	dec := json.NewDecoder(bytes.NewReader(buf.Bytes()))
   262  
   263  	var ops []string
   264  	for dec.More() {
   265  		var d streamDecode
   266  
   267  		err := dec.Decode(&d)
   268  		if err != nil {
   269  			t.Fatal(err)
   270  		}
   271  
   272  		if d.Type != typeOperation {
   273  			continue
   274  		}
   275  
   276  		o := &marshalOperation{}
   277  		err = json.Unmarshal(d.JSON, o)
   278  		if err != nil {
   279  			t.Fatal(err)
   280  		}
   281  
   282  		switch {
   283  		case o.Begin == "AddOne":
   284  			ops = append(ops, "BeginAddOne")
   285  		case o.End == "AddOne":
   286  			ops = append(ops, "EndAddOne")
   287  		case o.Begin == "AddTwo":
   288  			ops = append(ops, "BeginAddTwo")
   289  		case o.End == "AddTwo":
   290  			ops = append(ops, "EndAddTwo")
   291  		case o.Begin == "NestedAddThree":
   292  			ops = append(ops, "BeginAddThree")
   293  		case o.End == "NestedAddThree":
   294  			ops = append(ops, "EndAddThree")
   295  		}
   296  	}
   297  
   298  	expectedOps := []string{
   299  		"BeginAddOne",
   300  		"EndAddOne",
   301  		"BeginAddTwo",
   302  		"BeginAddThree",
   303  		"EndAddThree",
   304  		"EndAddTwo",
   305  	}
   306  
   307  	if strings.Join(ops, ",") != strings.Join(expectedOps, ",") {
   308  		t.Fatalf("incorrect order of operations: %v", ops)
   309  	}
   310  }
   311  
   312  // Verify that we can replay visiting each vertex in order
   313  func TestGraphJSON_debugVisits(t *testing.T) {
   314  	var g Graph
   315  	var buf bytes.Buffer
   316  	g.SetDebugWriter(&buf)
   317  
   318  	g.Add(1)
   319  	g.Add(2)
   320  	g.Add(3)
   321  	g.Add(4)
   322  
   323  	g.Connect(BasicEdge(2, 1))
   324  	g.Connect(BasicEdge(4, 2))
   325  	g.Connect(BasicEdge(3, 4))
   326  
   327  	err := (&AcyclicGraph{g}).Walk(func(v Vertex) tfdiags.Diagnostics {
   328  		g.DebugVisitInfo(v, "basic walk")
   329  		return nil
   330  	})
   331  
   332  	if err != nil {
   333  		t.Fatal(err)
   334  	}
   335  
   336  	var visited []string
   337  
   338  	dec := json.NewDecoder(bytes.NewReader(buf.Bytes()))
   339  	for dec.More() {
   340  		var d streamDecode
   341  
   342  		err := dec.Decode(&d)
   343  		if err != nil {
   344  			t.Fatal(err)
   345  		}
   346  
   347  		if d.Type != typeVisitInfo {
   348  			continue
   349  		}
   350  
   351  		o := &marshalVertexInfo{}
   352  		err = json.Unmarshal(d.JSON, o)
   353  		if err != nil {
   354  			t.Fatal(err)
   355  		}
   356  
   357  		visited = append(visited, o.Vertex.ID)
   358  	}
   359  
   360  	expected := []string{"1", "2", "4", "3"}
   361  
   362  	if strings.Join(visited, "-") != strings.Join(expected, "-") {
   363  		t.Fatalf("incorrect order of operations: %v", visited)
   364  	}
   365  }
   366  
   367  const testGraphJSONEmptyStr = `{
   368    "Type": "Graph",
   369    "Name": "root",
   370    "Vertices": [
   371      {
   372        "ID": "1",
   373        "Name": "1"
   374      },
   375      {
   376        "ID": "2",
   377        "Name": "2"
   378      },
   379      {
   380        "ID": "3",
   381        "Name": "3"
   382      }
   383    ]
   384  }`
   385  
   386  const testGraphJSONBasicStr = `{
   387    "Type": "Graph",
   388    "Name": "root",
   389    "Vertices": [
   390      {
   391        "ID": "1",
   392        "Name": "1"
   393      },
   394      {
   395        "ID": "2",
   396        "Name": "2"
   397      },
   398      {
   399        "ID": "3",
   400        "Name": "3"
   401      }
   402    ],
   403    "Edges": [
   404      {
   405        "Name": "1|3",
   406        "Source": "1",
   407        "Target": "3"
   408      }
   409    ]
   410  }`