mirror of
https://github.com/gonum/gonum.git
synced 2025-10-05 23:26:52 +08:00
197 lines
4.8 KiB
Go
197 lines
4.8 KiB
Go
// Copyright ©2017 The gonum Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package dot
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"gonum.org/v1/gonum/graph"
|
|
"gonum.org/v1/gonum/graph/simple"
|
|
)
|
|
|
|
func TestRoundTrip(t *testing.T) {
|
|
golden := []struct {
|
|
want string
|
|
directed bool
|
|
}{
|
|
{
|
|
want: directed,
|
|
directed: true,
|
|
},
|
|
{
|
|
want: undirected,
|
|
directed: false,
|
|
},
|
|
}
|
|
for i, g := range golden {
|
|
var dst Builder
|
|
if g.directed {
|
|
dst = newDotDirectedGraph()
|
|
} else {
|
|
dst = newDotUndirectedGraph()
|
|
}
|
|
data := []byte(g.want)
|
|
if err := Unmarshal(data, dst); err != nil {
|
|
t.Errorf("i=%d: unable to unmarshal DOT graph; %v", i, err)
|
|
continue
|
|
}
|
|
buf, err := Marshal(dst, "", "", "\t", false)
|
|
if err != nil {
|
|
t.Errorf("i=%d: unable to marshal graph; %v", i, dst)
|
|
continue
|
|
}
|
|
got := string(buf)
|
|
if got != g.want {
|
|
t.Errorf("i=%d: graph content mismatch; expected `%s`, got `%s`", i, g.want, got)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
const directed = `digraph {
|
|
// Node definitions.
|
|
0 [label="foo 2"];
|
|
1 [label="bar 2"];
|
|
|
|
// Edge definitions.
|
|
0 -> 1 [label="baz 2"];
|
|
}`
|
|
|
|
const undirected = `graph {
|
|
// Node definitions.
|
|
0 [label="foo 2"];
|
|
1 [label="bar 2"];
|
|
|
|
// Edge definitions.
|
|
0 -- 1 [label="baz 2"];
|
|
}`
|
|
|
|
// Below follows a minimal implementation of a graph capable of validating the
|
|
// round-trip encoding and decoding of DOT graphs with nodes and edges
|
|
// containing DOT attributes.
|
|
|
|
// dotDirectedGraph extends simple.DirectedGraph to add NewNode and NewEdge
|
|
// methods for creating user-defined nodes and edges.
|
|
//
|
|
// dotDirectedGraph implements the dot.Builder interface.
|
|
type dotDirectedGraph struct {
|
|
*simple.DirectedGraph
|
|
}
|
|
|
|
// newDotDirectedGraph returns a new directed capable of creating user-defined
|
|
// nodes and edges.
|
|
func newDotDirectedGraph() *dotDirectedGraph {
|
|
return &dotDirectedGraph{DirectedGraph: simple.NewDirectedGraph(0, 0)}
|
|
}
|
|
|
|
// NewNode adds a new node with a unique node ID to the graph.
|
|
func (g *dotDirectedGraph) NewNode() graph.Node {
|
|
n := &dotNode{Node: simple.Node(g.NewNodeID())}
|
|
g.AddNode(n)
|
|
return n
|
|
}
|
|
|
|
// NewEdge adds a new edge from the source to the destination node to the graph,
|
|
// or returns the existing edge if already present.
|
|
func (g *dotDirectedGraph) NewEdge(from, to graph.Node) graph.Edge {
|
|
if e := g.Edge(from, to); e != nil {
|
|
return e
|
|
}
|
|
e := &dotEdge{Edge: simple.Edge{F: from, T: to}}
|
|
g.SetEdge(e)
|
|
return e
|
|
}
|
|
|
|
// dotUndirectedGraph extends simple.UndirectedGraph to add NewNode and NewEdge
|
|
// methods for creating user-defined nodes and edges.
|
|
//
|
|
// dotUndirectedGraph implements the dot.Builder interface.
|
|
type dotUndirectedGraph struct {
|
|
*simple.UndirectedGraph
|
|
}
|
|
|
|
// newDotUndirectedGraph returns a new undirected capable of creating user-
|
|
// defined nodes and edges.
|
|
func newDotUndirectedGraph() *dotUndirectedGraph {
|
|
return &dotUndirectedGraph{UndirectedGraph: simple.NewUndirectedGraph(0, 0)}
|
|
}
|
|
|
|
// NewNode adds a new node with a unique node ID to the graph.
|
|
func (g *dotUndirectedGraph) NewNode() graph.Node {
|
|
n := &dotNode{Node: simple.Node(g.NewNodeID())}
|
|
g.AddNode(n)
|
|
return n
|
|
}
|
|
|
|
// NewEdge adds a new edge from the source to the destination node to the graph,
|
|
// or returns the existing edge if already present.
|
|
func (g *dotUndirectedGraph) NewEdge(from, to graph.Node) graph.Edge {
|
|
if e := g.Edge(from, to); e != nil {
|
|
return e
|
|
}
|
|
e := &dotEdge{Edge: simple.Edge{F: from, T: to}}
|
|
g.SetEdge(e)
|
|
return e
|
|
}
|
|
|
|
// dotNode extends simple.Node with a label field to test round-trip encoding
|
|
// and decoding of node DOT label attributes.
|
|
type dotNode struct {
|
|
simple.Node
|
|
// Node label.
|
|
Label string
|
|
}
|
|
|
|
// UnmarshalDOTAttr decodes a single DOT attribute.
|
|
func (n *dotNode) UnmarshalDOTAttr(attr Attribute) error {
|
|
if attr.Key != "label" {
|
|
return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key)
|
|
}
|
|
n.Label = attr.Value
|
|
return nil
|
|
}
|
|
|
|
// DOTAttributes returns the DOT attributes of the node.
|
|
func (n *dotNode) DOTAttributes() []Attribute {
|
|
if len(n.Label) == 0 {
|
|
return nil
|
|
}
|
|
attr := Attribute{
|
|
Key: "label",
|
|
Value: n.Label,
|
|
}
|
|
return []Attribute{attr}
|
|
}
|
|
|
|
// dotEdge extends simple.Edge with a label field to test round-trip encoding and
|
|
// decoding of edge DOT label attributes.
|
|
type dotEdge struct {
|
|
simple.Edge
|
|
// Edge label.
|
|
Label string
|
|
}
|
|
|
|
// UnmarshalDOTAttr decodes a single DOT attribute.
|
|
func (e *dotEdge) UnmarshalDOTAttr(attr Attribute) error {
|
|
if attr.Key != "label" {
|
|
return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key)
|
|
}
|
|
e.Label = attr.Value
|
|
return nil
|
|
}
|
|
|
|
// DOTAttributes returns the DOT attributes of the edge.
|
|
func (e *dotEdge) DOTAttributes() []Attribute {
|
|
if len(e.Label) == 0 {
|
|
return nil
|
|
}
|
|
attr := Attribute{
|
|
Key: "label",
|
|
Value: e.Label,
|
|
}
|
|
return []Attribute{attr}
|
|
}
|