parent
6096371068
commit
0c1ab6142b
|
@ -100,8 +100,6 @@ func (g *AcyclicGraph) TransitiveReduction() {
|
|||
// v such that the edge (u,v) exists (v is a direct descendant of u).
|
||||
//
|
||||
// For each v-prime reachable from v, remove the edge (u, v-prime).
|
||||
defer g.debug.BeginOperation("TransitiveReduction", "").End("")
|
||||
|
||||
for _, u := range g.Vertices() {
|
||||
uTargets := g.DownEdges(u)
|
||||
|
||||
|
@ -163,8 +161,6 @@ func (g *AcyclicGraph) Cycles() [][]Vertex {
|
|||
// This will walk nodes in parallel if it can. The resulting diagnostics
|
||||
// contains problems from all graphs visited, in no particular order.
|
||||
func (g *AcyclicGraph) Walk(cb WalkFunc) tfdiags.Diagnostics {
|
||||
defer g.debug.BeginOperation(typeWalk, "").End("")
|
||||
|
||||
w := &Walker{Callback: cb, Reverse: true}
|
||||
w.Update(g)
|
||||
return w.Wait()
|
||||
|
@ -226,8 +222,6 @@ func (g *AcyclicGraph) DepthFirstWalk(start Set, f DepthWalkFunc) error {
|
|||
// SortedDepthFirstWalk does a depth-first walk of the graph starting from
|
||||
// the vertices in start, always iterating the nodes in a consistent order.
|
||||
func (g *AcyclicGraph) SortedDepthFirstWalk(start []Vertex, f DepthWalkFunc) error {
|
||||
defer g.debug.BeginOperation(typeDepthFirstWalk, "").End("")
|
||||
|
||||
seen := make(map[Vertex]struct{})
|
||||
frontier := make([]*vertexAtDepth, len(start))
|
||||
for i, v := range start {
|
||||
|
@ -310,8 +304,6 @@ func (g *AcyclicGraph) ReverseDepthFirstWalk(start Set, f DepthWalkFunc) error {
|
|||
// SortedReverseDepthFirstWalk does a depth-first walk _up_ the graph starting from
|
||||
// the vertices in start, always iterating the nodes in a consistent order.
|
||||
func (g *AcyclicGraph) SortedReverseDepthFirstWalk(start []Vertex, f DepthWalkFunc) error {
|
||||
defer g.debug.BeginOperation(typeReverseDepthFirstWalk, "").End("")
|
||||
|
||||
seen := make(map[Vertex]struct{})
|
||||
frontier := make([]*vertexAtDepth, len(start))
|
||||
for i, v := range start {
|
||||
|
|
60
dag/graph.go
60
dag/graph.go
|
@ -2,9 +2,7 @@ package dag
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
|
@ -14,9 +12,6 @@ type Graph struct {
|
|||
edges Set
|
||||
downEdges map[interface{}]Set
|
||||
upEdges map[interface{}]Set
|
||||
|
||||
// JSON encoder for recording debug information
|
||||
debug *encoder
|
||||
}
|
||||
|
||||
// Subgrapher allows a Vertex to be a Graph itself, by returning a Grapher.
|
||||
|
@ -106,7 +101,6 @@ func (g *Graph) HasEdge(e Edge) bool {
|
|||
func (g *Graph) Add(v Vertex) Vertex {
|
||||
g.init()
|
||||
g.vertices.Add(v)
|
||||
g.debug.Add(v)
|
||||
return v
|
||||
}
|
||||
|
||||
|
@ -115,7 +109,6 @@ func (g *Graph) Add(v Vertex) Vertex {
|
|||
func (g *Graph) Remove(v Vertex) Vertex {
|
||||
// Delete the vertex itself
|
||||
g.vertices.Delete(v)
|
||||
g.debug.Remove(v)
|
||||
|
||||
// Delete the edges to non-existent things
|
||||
for _, target := range g.DownEdges(v) {
|
||||
|
@ -137,8 +130,6 @@ func (g *Graph) Replace(original, replacement Vertex) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
defer g.debug.BeginOperation("Replace", "").End("")
|
||||
|
||||
// If they're the same, then don't do anything
|
||||
if original == replacement {
|
||||
return true
|
||||
|
@ -162,7 +153,6 @@ func (g *Graph) Replace(original, replacement Vertex) bool {
|
|||
// RemoveEdge removes an edge from the graph.
|
||||
func (g *Graph) RemoveEdge(edge Edge) {
|
||||
g.init()
|
||||
g.debug.RemoveEdge(edge)
|
||||
|
||||
// Delete the edge from the set
|
||||
g.edges.Delete(edge)
|
||||
|
@ -194,7 +184,6 @@ func (g *Graph) UpEdges(v Vertex) Set {
|
|||
// value of the edge itself.
|
||||
func (g *Graph) Connect(edge Edge) {
|
||||
g.init()
|
||||
g.debug.Connect(edge)
|
||||
|
||||
source := edge.Source()
|
||||
target := edge.Target()
|
||||
|
@ -327,55 +316,6 @@ func (g *Graph) Dot(opts *DotOpts) []byte {
|
|||
return newMarshalGraph("", g).Dot(opts)
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON representation of the entire Graph.
|
||||
func (g *Graph) MarshalJSON() ([]byte, error) {
|
||||
dg := newMarshalGraph("root", g)
|
||||
return json.MarshalIndent(dg, "", " ")
|
||||
}
|
||||
|
||||
// SetDebugWriter sets the io.Writer where the Graph will record debug
|
||||
// information. After this is set, the graph will immediately encode itself to
|
||||
// the stream, and continue to record all subsequent operations.
|
||||
func (g *Graph) SetDebugWriter(w io.Writer) {
|
||||
g.debug = &encoder{w: w}
|
||||
g.debug.Encode(newMarshalGraph("root", g))
|
||||
}
|
||||
|
||||
// DebugVertexInfo encodes arbitrary information about a vertex in the graph
|
||||
// debug logs.
|
||||
func (g *Graph) DebugVertexInfo(v Vertex, info string) {
|
||||
va := newVertexInfo(typeVertexInfo, v, info)
|
||||
g.debug.Encode(va)
|
||||
}
|
||||
|
||||
// DebugEdgeInfo encodes arbitrary information about an edge in the graph debug
|
||||
// logs.
|
||||
func (g *Graph) DebugEdgeInfo(e Edge, info string) {
|
||||
ea := newEdgeInfo(typeEdgeInfo, e, info)
|
||||
g.debug.Encode(ea)
|
||||
}
|
||||
|
||||
// DebugVisitInfo records a visit to a Vertex during a walk operation.
|
||||
func (g *Graph) DebugVisitInfo(v Vertex, info string) {
|
||||
vi := newVertexInfo(typeVisitInfo, v, info)
|
||||
g.debug.Encode(vi)
|
||||
}
|
||||
|
||||
// DebugOperation marks the start of a set of graph transformations in
|
||||
// the debug log, and returns a DebugOperationEnd func, which marks the end of
|
||||
// the operation in the log. Additional information can be added to the log via
|
||||
// the info parameter.
|
||||
//
|
||||
// The returned func's End method allows this method to be called from a single
|
||||
// defer statement:
|
||||
// defer g.DebugOperationBegin("OpName", "operating").End("")
|
||||
//
|
||||
// The returned function must be called to properly close the logical operation
|
||||
// in the logs.
|
||||
func (g *Graph) DebugOperation(operation string, info string) DebugOperationEnd {
|
||||
return g.debug.BeginOperation(operation, info)
|
||||
}
|
||||
|
||||
// VertexName returns the name of a vertex.
|
||||
func VertexName(raw Vertex) string {
|
||||
switch v := raw.(type) {
|
||||
|
|
242
dag/marshal.go
242
dag/marshal.go
|
@ -1,14 +1,10 @@
|
|||
package dag
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -234,241 +230,3 @@ func marshalSubgrapher(v Vertex) (*Graph, bool) {
|
|||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// The DebugOperationEnd func type provides a way to call an End function via a
|
||||
// method call, allowing for the chaining of methods in a defer statement.
|
||||
type DebugOperationEnd func(string)
|
||||
|
||||
// End calls function e with the info parameter, marking the end of this
|
||||
// operation in the logs.
|
||||
func (e DebugOperationEnd) End(info string) { e(info) }
|
||||
|
||||
// encoder provides methods to write debug data to an io.Writer, and is a noop
|
||||
// when no writer is present
|
||||
type encoder struct {
|
||||
sync.Mutex
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// Encode is analogous to json.Encoder.Encode
|
||||
func (e *encoder) Encode(i interface{}) {
|
||||
if e == nil || e.w == nil {
|
||||
return
|
||||
}
|
||||
e.Lock()
|
||||
defer e.Unlock()
|
||||
|
||||
js, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] dag:", err)
|
||||
return
|
||||
}
|
||||
js = append(js, '\n')
|
||||
|
||||
_, err = e.w.Write(js)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] dag:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) Add(v Vertex) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
e.Encode(marshalTransform{
|
||||
Type: typeTransform,
|
||||
AddVertex: newMarshalVertex(v),
|
||||
})
|
||||
}
|
||||
|
||||
// Remove records the removal of Vertex v.
|
||||
func (e *encoder) Remove(v Vertex) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
e.Encode(marshalTransform{
|
||||
Type: typeTransform,
|
||||
RemoveVertex: newMarshalVertex(v),
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) Connect(edge Edge) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
e.Encode(marshalTransform{
|
||||
Type: typeTransform,
|
||||
AddEdge: newMarshalEdge(edge),
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) RemoveEdge(edge Edge) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
e.Encode(marshalTransform{
|
||||
Type: typeTransform,
|
||||
RemoveEdge: newMarshalEdge(edge),
|
||||
})
|
||||
}
|
||||
|
||||
// BeginOperation marks the start of set of graph transformations, and returns
|
||||
// an EndDebugOperation func to be called once the opration is complete.
|
||||
func (e *encoder) BeginOperation(op string, info string) DebugOperationEnd {
|
||||
if e == nil {
|
||||
return func(string) {}
|
||||
}
|
||||
|
||||
e.Encode(marshalOperation{
|
||||
Type: typeOperation,
|
||||
Begin: op,
|
||||
Info: info,
|
||||
})
|
||||
|
||||
return func(info string) {
|
||||
e.Encode(marshalOperation{
|
||||
Type: typeOperation,
|
||||
End: op,
|
||||
Info: info,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// structure for recording graph transformations
|
||||
type marshalTransform struct {
|
||||
// Type: "Transform"
|
||||
Type string
|
||||
AddEdge *marshalEdge `json:",omitempty"`
|
||||
RemoveEdge *marshalEdge `json:",omitempty"`
|
||||
AddVertex *marshalVertex `json:",omitempty"`
|
||||
RemoveVertex *marshalVertex `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (t marshalTransform) Transform(g *marshalGraph) {
|
||||
switch {
|
||||
case t.AddEdge != nil:
|
||||
g.connect(t.AddEdge)
|
||||
case t.RemoveEdge != nil:
|
||||
g.removeEdge(t.RemoveEdge)
|
||||
case t.AddVertex != nil:
|
||||
g.add(t.AddVertex)
|
||||
case t.RemoveVertex != nil:
|
||||
g.remove(t.RemoveVertex)
|
||||
}
|
||||
}
|
||||
|
||||
// this structure allows us to decode any object in the json stream for
|
||||
// inspection, then re-decode it into a proper struct if needed.
|
||||
type streamDecode struct {
|
||||
Type string
|
||||
Map map[string]interface{}
|
||||
JSON []byte
|
||||
}
|
||||
|
||||
func (s *streamDecode) UnmarshalJSON(d []byte) error {
|
||||
s.JSON = d
|
||||
err := json.Unmarshal(d, &s.Map)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t, ok := s.Map["Type"]; ok {
|
||||
s.Type, _ = t.(string)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// structure for recording the beginning and end of any multi-step
|
||||
// transformations. These are informational, and not required to reproduce the
|
||||
// graph state.
|
||||
type marshalOperation struct {
|
||||
Type string
|
||||
Begin string `json:",omitempty"`
|
||||
End string `json:",omitempty"`
|
||||
Info string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// decodeGraph decodes a marshalGraph from an encoded graph stream.
|
||||
func decodeGraph(r io.Reader) (*marshalGraph, error) {
|
||||
dec := json.NewDecoder(r)
|
||||
|
||||
// a stream should always start with a graph
|
||||
g := &marshalGraph{}
|
||||
|
||||
err := dec.Decode(g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// now replay any operations that occurred on the original graph
|
||||
for dec.More() {
|
||||
s := &streamDecode{}
|
||||
err := dec.Decode(s)
|
||||
if err != nil {
|
||||
return g, err
|
||||
}
|
||||
|
||||
// the only Type we're concerned with here is Transform to complete the
|
||||
// Graph
|
||||
if s.Type != typeTransform {
|
||||
continue
|
||||
}
|
||||
|
||||
t := &marshalTransform{}
|
||||
err = json.Unmarshal(s.JSON, t)
|
||||
if err != nil {
|
||||
return g, err
|
||||
}
|
||||
t.Transform(g)
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// marshalVertexInfo allows encoding arbitrary information about the a single
|
||||
// Vertex in the logs. These are accumulated for informational display while
|
||||
// rebuilding the graph.
|
||||
type marshalVertexInfo struct {
|
||||
Type string
|
||||
Vertex *marshalVertex
|
||||
Info string
|
||||
}
|
||||
|
||||
func newVertexInfo(infoType string, v Vertex, info string) *marshalVertexInfo {
|
||||
return &marshalVertexInfo{
|
||||
Type: infoType,
|
||||
Vertex: newMarshalVertex(v),
|
||||
Info: info,
|
||||
}
|
||||
}
|
||||
|
||||
// marshalEdgeInfo allows encoding arbitrary information about the a single
|
||||
// Edge in the logs. These are accumulated for informational display while
|
||||
// rebuilding the graph.
|
||||
type marshalEdgeInfo struct {
|
||||
Type string
|
||||
Edge *marshalEdge
|
||||
Info string
|
||||
}
|
||||
|
||||
func newEdgeInfo(infoType string, e Edge, info string) *marshalEdgeInfo {
|
||||
return &marshalEdgeInfo{
|
||||
Type: infoType,
|
||||
Edge: newMarshalEdge(e),
|
||||
Info: info,
|
||||
}
|
||||
}
|
||||
|
||||
// JSON2Dot reads a Graph debug log from and io.Reader, and converts the final
|
||||
// graph dot format.
|
||||
//
|
||||
// TODO: Allow returning the output at a certain point during decode.
|
||||
// Encode extra information from the json log into the Dot.
|
||||
func JSON2Dot(r io.Reader) ([]byte, error) {
|
||||
g, err := decodeGraph(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return g.Dot(nil), nil
|
||||
}
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
package dag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
func TestGraphDot_empty(t *testing.T) {
|
||||
|
@ -80,288 +76,3 @@ const testGraphDotAttrsStr = `digraph {
|
|||
"[root] foo" [foo = "bar"]
|
||||
}
|
||||
}`
|
||||
|
||||
func TestGraphJSON_empty(t *testing.T) {
|
||||
var g Graph
|
||||
g.Add(1)
|
||||
g.Add(2)
|
||||
g.Add(3)
|
||||
|
||||
js, err := g.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(string(js))
|
||||
expected := strings.TrimSpace(testGraphJSONEmptyStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraphJSON_basic(t *testing.T) {
|
||||
var g Graph
|
||||
g.Add(1)
|
||||
g.Add(2)
|
||||
g.Add(3)
|
||||
g.Connect(BasicEdge(1, 3))
|
||||
|
||||
js, err := g.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := strings.TrimSpace(string(js))
|
||||
expected := strings.TrimSpace(testGraphJSONBasicStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that Vertex and Edge annotations appear in the debug output
|
||||
func TestGraphJSON_debugInfo(t *testing.T) {
|
||||
var g Graph
|
||||
var buf bytes.Buffer
|
||||
g.SetDebugWriter(&buf)
|
||||
|
||||
g.Add(1)
|
||||
g.Add(2)
|
||||
g.Add(3)
|
||||
g.Connect(BasicEdge(1, 2))
|
||||
|
||||
g.DebugVertexInfo(2, "2")
|
||||
g.DebugVertexInfo(3, "3")
|
||||
g.DebugEdgeInfo(BasicEdge(1, 2), "1|2")
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(buf.Bytes()))
|
||||
|
||||
var found2, found3, foundEdge bool
|
||||
for dec.More() {
|
||||
var d streamDecode
|
||||
|
||||
err := dec.Decode(&d)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
switch d.Type {
|
||||
case typeVertexInfo:
|
||||
va := &marshalVertexInfo{}
|
||||
err := json.Unmarshal(d.JSON, va)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
switch va.Info {
|
||||
case "2":
|
||||
if va.Vertex.Name != "2" {
|
||||
t.Fatalf("wrong vertex annotated 2: %#v", va)
|
||||
}
|
||||
found2 = true
|
||||
case "3":
|
||||
if va.Vertex.Name != "3" {
|
||||
t.Fatalf("wrong vertex annotated 3: %#v", va)
|
||||
}
|
||||
found3 = true
|
||||
default:
|
||||
t.Fatalf("unexpected annotation: %#v", va)
|
||||
}
|
||||
case typeEdgeInfo:
|
||||
ea := &marshalEdgeInfo{}
|
||||
err := json.Unmarshal(d.JSON, ea)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
switch ea.Info {
|
||||
case "1|2":
|
||||
if ea.Edge.Name != "1|2" {
|
||||
t.Fatalf("incorrect edge annotation: %#v\n", ea)
|
||||
}
|
||||
foundEdge = true
|
||||
default:
|
||||
t.Fatalf("unexpected edge Info: %#v", ea)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found2 {
|
||||
t.Fatal("annotation 2 not found")
|
||||
}
|
||||
if !found3 {
|
||||
t.Fatal("annotation 3 not found")
|
||||
}
|
||||
if !foundEdge {
|
||||
t.Fatal("edge annotation not found")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that debug operations appear in the debug output
|
||||
func TestGraphJSON_debugOperations(t *testing.T) {
|
||||
var g Graph
|
||||
var buf bytes.Buffer
|
||||
g.SetDebugWriter(&buf)
|
||||
|
||||
debugOp := g.DebugOperation("AddOne", "adding node 1")
|
||||
g.Add(1)
|
||||
debugOp.End("done adding node 1")
|
||||
|
||||
// use an immediate closure to test defers
|
||||
func() {
|
||||
defer g.DebugOperation("AddTwo", "adding nodes 2 and 3").End("done adding 2 and 3")
|
||||
g.Add(2)
|
||||
defer g.DebugOperation("NestedAddThree", "second defer").End("done adding node 3")
|
||||
g.Add(3)
|
||||
}()
|
||||
|
||||
g.Connect(BasicEdge(1, 2))
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(buf.Bytes()))
|
||||
|
||||
var ops []string
|
||||
for dec.More() {
|
||||
var d streamDecode
|
||||
|
||||
err := dec.Decode(&d)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if d.Type != typeOperation {
|
||||
continue
|
||||
}
|
||||
|
||||
o := &marshalOperation{}
|
||||
err = json.Unmarshal(d.JSON, o)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case o.Begin == "AddOne":
|
||||
ops = append(ops, "BeginAddOne")
|
||||
case o.End == "AddOne":
|
||||
ops = append(ops, "EndAddOne")
|
||||
case o.Begin == "AddTwo":
|
||||
ops = append(ops, "BeginAddTwo")
|
||||
case o.End == "AddTwo":
|
||||
ops = append(ops, "EndAddTwo")
|
||||
case o.Begin == "NestedAddThree":
|
||||
ops = append(ops, "BeginAddThree")
|
||||
case o.End == "NestedAddThree":
|
||||
ops = append(ops, "EndAddThree")
|
||||
}
|
||||
}
|
||||
|
||||
expectedOps := []string{
|
||||
"BeginAddOne",
|
||||
"EndAddOne",
|
||||
"BeginAddTwo",
|
||||
"BeginAddThree",
|
||||
"EndAddThree",
|
||||
"EndAddTwo",
|
||||
}
|
||||
|
||||
if strings.Join(ops, ",") != strings.Join(expectedOps, ",") {
|
||||
t.Fatalf("incorrect order of operations: %v", ops)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that we can replay visiting each vertex in order
|
||||
func TestGraphJSON_debugVisits(t *testing.T) {
|
||||
var g Graph
|
||||
var buf bytes.Buffer
|
||||
g.SetDebugWriter(&buf)
|
||||
|
||||
g.Add(1)
|
||||
g.Add(2)
|
||||
g.Add(3)
|
||||
g.Add(4)
|
||||
|
||||
g.Connect(BasicEdge(2, 1))
|
||||
g.Connect(BasicEdge(4, 2))
|
||||
g.Connect(BasicEdge(3, 4))
|
||||
|
||||
err := (&AcyclicGraph{g}).Walk(func(v Vertex) tfdiags.Diagnostics {
|
||||
g.DebugVisitInfo(v, "basic walk")
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var visited []string
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(buf.Bytes()))
|
||||
for dec.More() {
|
||||
var d streamDecode
|
||||
|
||||
err := dec.Decode(&d)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if d.Type != typeVisitInfo {
|
||||
continue
|
||||
}
|
||||
|
||||
o := &marshalVertexInfo{}
|
||||
err = json.Unmarshal(d.JSON, o)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
visited = append(visited, o.Vertex.ID)
|
||||
}
|
||||
|
||||
expected := []string{"1", "2", "4", "3"}
|
||||
|
||||
if strings.Join(visited, "-") != strings.Join(expected, "-") {
|
||||
t.Fatalf("incorrect order of operations: %v", visited)
|
||||
}
|
||||
}
|
||||
|
||||
const testGraphJSONEmptyStr = `{
|
||||
"Type": "Graph",
|
||||
"Name": "root",
|
||||
"Vertices": [
|
||||
{
|
||||
"ID": "1",
|
||||
"Name": "1"
|
||||
},
|
||||
{
|
||||
"ID": "2",
|
||||
"Name": "2"
|
||||
},
|
||||
{
|
||||
"ID": "3",
|
||||
"Name": "3"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
const testGraphJSONBasicStr = `{
|
||||
"Type": "Graph",
|
||||
"Name": "root",
|
||||
"Vertices": [
|
||||
{
|
||||
"ID": "1",
|
||||
"Name": "1"
|
||||
},
|
||||
{
|
||||
"ID": "2",
|
||||
"Name": "2"
|
||||
},
|
||||
{
|
||||
"ID": "3",
|
||||
"Name": "3"
|
||||
}
|
||||
],
|
||||
"Edges": [
|
||||
{
|
||||
"Name": "1|3",
|
||||
"Source": "1",
|
||||
"Target": "3"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
|
Loading…
Reference in New Issue