gonum.org/v1/gonum@v0.14.0/graph/encoding/dot/decode_test.go (about)

     1  // Copyright ©2017 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package dot
     6  
     7  import (
     8  	"fmt"
     9  	"testing"
    10  
    11  	"gonum.org/v1/gonum/graph"
    12  	"gonum.org/v1/gonum/graph/encoding"
    13  	"gonum.org/v1/gonum/graph/multi"
    14  	"gonum.org/v1/gonum/graph/simple"
    15  )
    16  
    17  func TestRoundTrip(t *testing.T) {
    18  	golden := []struct {
    19  		want     string
    20  		directed bool
    21  	}{
    22  		{
    23  			want:     directed,
    24  			directed: true,
    25  		},
    26  		{
    27  			want:     undirected,
    28  			directed: false,
    29  		},
    30  		{
    31  			want:     directedID,
    32  			directed: true,
    33  		},
    34  		{
    35  			want:     undirectedID,
    36  			directed: false,
    37  		},
    38  		{
    39  			want:     directedWithPorts,
    40  			directed: true,
    41  		},
    42  		{
    43  			want:     undirectedWithPorts,
    44  			directed: false,
    45  		},
    46  		{
    47  			want:     directedAttrs,
    48  			directed: true,
    49  		},
    50  		{
    51  			want:     undirectedAttrs,
    52  			directed: false,
    53  		},
    54  	}
    55  	for i, g := range golden {
    56  		var dst encoding.Builder
    57  		if g.directed {
    58  			dst = newDotDirectedGraph()
    59  		} else {
    60  			dst = newDotUndirectedGraph()
    61  		}
    62  		data := []byte(g.want)
    63  		if err := Unmarshal(data, dst); err != nil {
    64  			t.Errorf("i=%d: unable to unmarshal DOT graph; %v", i, err)
    65  			continue
    66  		}
    67  		buf, err := Marshal(dst, "", "", "\t")
    68  		if err != nil {
    69  			t.Errorf("i=%d: unable to marshal graph; %v", i, dst)
    70  			continue
    71  		}
    72  		got := string(buf)
    73  		if got != g.want {
    74  			t.Errorf("i=%d: graph content mismatch; want:\n%s\n\ngot:\n%s", i, g.want, got)
    75  			continue
    76  		}
    77  	}
    78  }
    79  
    80  const directed = `strict digraph {
    81  	graph [
    82  		outputorder=edgesfirst
    83  	];
    84  	node [
    85  		shape=circle
    86  		style=filled
    87  	];
    88  	edge [
    89  		penwidth=5
    90  		color=gray
    91  	];
    92  
    93  	// Node definitions.
    94  	A [label="foo 2"];
    95  	B [label="bar 2"];
    96  
    97  	// Edge definitions.
    98  	A -> B [label="baz 2"];
    99  }`
   100  
   101  const undirected = `strict graph {
   102  	graph [
   103  		outputorder=edgesfirst
   104  	];
   105  	node [
   106  		shape=circle
   107  		style=filled
   108  	];
   109  	edge [
   110  		penwidth=5
   111  		color=gray
   112  	];
   113  
   114  	// Node definitions.
   115  	A [label="foo 2"];
   116  	B [label="bar 2"];
   117  
   118  	// Edge definitions.
   119  	A -- B [label="baz 2"];
   120  }`
   121  
   122  const directedID = `strict digraph G {
   123  	// Node definitions.
   124  	A;
   125  	B;
   126  
   127  	// Edge definitions.
   128  	A -> B;
   129  }`
   130  
   131  const undirectedID = `strict graph H {
   132  	// Node definitions.
   133  	A;
   134  	B;
   135  
   136  	// Edge definitions.
   137  	A -- B;
   138  }`
   139  
   140  const directedWithPorts = `strict digraph {
   141  	// Node definitions.
   142  	A;
   143  	B;
   144  	C;
   145  	D;
   146  	E;
   147  	F;
   148  
   149  	// Edge definitions.
   150  	A:foo -> B:bar;
   151  	A -> C:bar;
   152  	B:foo -> C;
   153  	D:foo:n -> E:bar:s;
   154  	D:e -> F:bar:w;
   155  	E:_ -> F:c;
   156  }`
   157  
   158  const undirectedWithPorts = `strict graph {
   159  	// Node definitions.
   160  	A;
   161  	B;
   162  	C;
   163  	D;
   164  	E;
   165  	F;
   166  
   167  	// Edge definitions.
   168  	A:foo -- B:bar;
   169  	A -- C:bar;
   170  	B:foo -- C;
   171  	D:foo:n -- E:bar:s;
   172  	D:e -- F:bar:w;
   173  	E:_ -- F:c;
   174  }`
   175  
   176  const directedAttrs = `strict digraph {
   177  	node [
   178  		shape=circle
   179  		style=filled
   180  		label="NODE"
   181  	];
   182  	edge [
   183  		penwidth=5
   184  		color=gray
   185  		label=3.14
   186  	];
   187  
   188  	// Node definitions.
   189  	A [label=<br>];
   190  	B [label=-14];
   191  
   192  	// Edge definitions.
   193  	A -> B [label="hello world"];
   194  }`
   195  
   196  const undirectedAttrs = `strict graph {
   197  	node [
   198  		shape=circle
   199  		style=filled
   200  		label="NODE"
   201  	];
   202  	edge [
   203  		penwidth=5
   204  		color=gray
   205  		label=3.14
   206  	];
   207  
   208  	// Node definitions.
   209  	A [label=<br>];
   210  	B [label=-14];
   211  
   212  	// Edge definitions.
   213  	A -- B [label="hello world"];
   214  }`
   215  
   216  func TestChainedEdgeAttributes(t *testing.T) {
   217  	golden := []struct {
   218  		in, want string
   219  		directed bool
   220  	}{
   221  		{
   222  			in:       directedChained,
   223  			want:     directedNonchained,
   224  			directed: true,
   225  		},
   226  		{
   227  			in:       undirectedChained,
   228  			want:     undirectedNonchained,
   229  			directed: false,
   230  		},
   231  	}
   232  	for i, g := range golden {
   233  		var dst encoding.Builder
   234  		if g.directed {
   235  			dst = newDotDirectedGraph()
   236  		} else {
   237  			dst = newDotUndirectedGraph()
   238  		}
   239  		data := []byte(g.in)
   240  		if err := Unmarshal(data, dst); err != nil {
   241  			t.Errorf("i=%d: unable to unmarshal DOT graph; %v", i, err)
   242  			continue
   243  		}
   244  		buf, err := Marshal(dst, "", "", "\t")
   245  		if err != nil {
   246  			t.Errorf("i=%d: unable to marshal graph; %v", i, dst)
   247  			continue
   248  		}
   249  		got := string(buf)
   250  		if got != g.want {
   251  			t.Errorf("i=%d: graph content mismatch; want:\n%s\n\ngot:\n%s", i, g.want, got)
   252  			continue
   253  		}
   254  	}
   255  }
   256  
   257  const directedChained = `strict digraph {
   258  	graph [
   259  		outputorder=edgesfirst
   260  	];
   261  	node [
   262  		shape=circle
   263  		style=filled
   264  	];
   265  	edge [
   266  		penwidth=5
   267  		color=gray
   268  	];
   269  
   270  	// Node definitions.
   271  	A [label="foo 2"];
   272  	B [label="bar 2"];
   273  
   274  	// Edge definitions.
   275  	A -> B -> A [label="baz 2"];
   276  }`
   277  
   278  const directedNonchained = `strict digraph {
   279  	graph [
   280  		outputorder=edgesfirst
   281  	];
   282  	node [
   283  		shape=circle
   284  		style=filled
   285  	];
   286  	edge [
   287  		penwidth=5
   288  		color=gray
   289  	];
   290  
   291  	// Node definitions.
   292  	A [label="foo 2"];
   293  	B [label="bar 2"];
   294  
   295  	// Edge definitions.
   296  	A -> B [label="baz 2"];
   297  	B -> A [label="baz 2"];
   298  }`
   299  
   300  const undirectedChained = `graph {
   301  	graph [
   302  		outputorder=edgesfirst
   303  	];
   304  	node [
   305  		shape=circle
   306  		style=filled
   307  	];
   308  	edge [
   309  		penwidth=5
   310  		color=gray
   311  	];
   312  
   313  	// Node definitions.
   314  	A [label="foo 2"];
   315  	B [label="bar 2"];
   316  	C [label="bif 2"];
   317  
   318  	// Edge definitions.
   319  	A -- B -- C [label="baz 2"];
   320  }`
   321  
   322  const undirectedNonchained = `strict graph {
   323  	graph [
   324  		outputorder=edgesfirst
   325  	];
   326  	node [
   327  		shape=circle
   328  		style=filled
   329  	];
   330  	edge [
   331  		penwidth=5
   332  		color=gray
   333  	];
   334  
   335  	// Node definitions.
   336  	A [label="foo 2"];
   337  	B [label="bar 2"];
   338  	C [label="bif 2"];
   339  
   340  	// Edge definitions.
   341  	A -- B [label="baz 2"];
   342  	B -- C [label="baz 2"];
   343  }`
   344  
   345  func TestMultigraphDecoding(t *testing.T) {
   346  	for i, test := range []struct {
   347  		directed bool
   348  		input    string
   349  		expected string
   350  	}{
   351  		{
   352  			directed: true,
   353  			input:    directedMultigraph,
   354  			expected: directedMultigraph,
   355  		},
   356  		{
   357  			directed: false,
   358  			input:    undirectedMultigraph,
   359  			expected: undirectedMultigraph,
   360  		},
   361  		{
   362  			directed: true,
   363  			input:    directedSelfLoopMultigraph,
   364  			expected: directedSelfLoopMultigraph,
   365  		},
   366  		{
   367  			directed: false,
   368  			input:    undirectedSelfLoopMultigraph,
   369  			expected: undirectedSelfLoopMultigraph,
   370  		},
   371  	} {
   372  		var dst encoding.MultiBuilder
   373  		if test.directed {
   374  			dst = multi.NewDirectedGraph()
   375  		} else {
   376  			dst = multi.NewUndirectedGraph()
   377  		}
   378  
   379  		if err := UnmarshalMulti([]byte(test.input), dst); err != nil {
   380  			t.Errorf("i=%d: unable to unmarshal DOT graph; %v", i, err)
   381  			continue
   382  		}
   383  		buf, err := MarshalMulti(dst, "", "", "\t")
   384  		if err != nil {
   385  			t.Errorf("i=%d: unable to marshal graph; %v", i, dst)
   386  			continue
   387  		}
   388  		actual := string(buf)
   389  		if actual != test.expected {
   390  			t.Errorf("i=%d: graph content mismatch; want:\n%s\n\nactual:\n%s", i, test.expected, actual)
   391  			continue
   392  		}
   393  	}
   394  }
   395  
   396  func TestMultigraphLineIDsharing(t *testing.T) {
   397  	for i, test := range []struct {
   398  		directed bool
   399  		lines    []multi.Line
   400  		expected string
   401  	}{
   402  		{
   403  			directed: true,
   404  			lines: []multi.Line{
   405  				{F: multi.Node(0), T: multi.Node(1), UID: 0},
   406  				{F: multi.Node(0), T: multi.Node(1), UID: 1},
   407  				{F: multi.Node(0), T: multi.Node(2), UID: 0},
   408  				{F: multi.Node(2), T: multi.Node(0), UID: 0},
   409  			},
   410  			expected: directedMultigraph,
   411  		},
   412  		{
   413  			directed: false,
   414  			lines: []multi.Line{
   415  				{F: multi.Node(0), T: multi.Node(1), UID: 0},
   416  				{F: multi.Node(0), T: multi.Node(1), UID: 1},
   417  				{F: multi.Node(0), T: multi.Node(2), UID: 0},
   418  				{F: multi.Node(0), T: multi.Node(2), UID: 1},
   419  			},
   420  			expected: undirectedMultigraph,
   421  		},
   422  		{
   423  			directed: true,
   424  			lines: []multi.Line{
   425  				{F: multi.Node(0), T: multi.Node(0), UID: 0},
   426  				{F: multi.Node(0), T: multi.Node(0), UID: 1},
   427  				{F: multi.Node(1), T: multi.Node(1), UID: 0},
   428  				{F: multi.Node(1), T: multi.Node(1), UID: 1},
   429  			},
   430  			expected: directedSelfLoopMultigraph,
   431  		},
   432  		{
   433  			directed: false,
   434  			lines: []multi.Line{
   435  				{F: multi.Node(0), T: multi.Node(0), UID: 0},
   436  				{F: multi.Node(0), T: multi.Node(0), UID: 1},
   437  				{F: multi.Node(1), T: multi.Node(1), UID: 0},
   438  				{F: multi.Node(1), T: multi.Node(1), UID: 1},
   439  			},
   440  			expected: undirectedSelfLoopMultigraph,
   441  		},
   442  	} {
   443  		var dst encoding.MultiBuilder
   444  		if test.directed {
   445  			dst = multi.NewDirectedGraph()
   446  		} else {
   447  			dst = multi.NewUndirectedGraph()
   448  		}
   449  
   450  		for _, l := range test.lines {
   451  			dst.SetLine(l)
   452  		}
   453  
   454  		buf, err := MarshalMulti(dst, "", "", "\t")
   455  		if err != nil {
   456  			t.Errorf("i=%d: unable to marshal graph; %v", i, dst)
   457  			continue
   458  		}
   459  		actual := string(buf)
   460  		if actual != test.expected {
   461  			t.Errorf("i=%d: graph content mismatch; want:\n%s\n\nactual:\n%s", i, test.expected, actual)
   462  			continue
   463  		}
   464  	}
   465  }
   466  
   467  const directedMultigraph = `digraph {
   468  	// Node definitions.
   469  	0;
   470  	1;
   471  	2;
   472  
   473  	// Edge definitions.
   474  	0 -> 1;
   475  	0 -> 1;
   476  	0 -> 2;
   477  	2 -> 0;
   478  }`
   479  
   480  const undirectedMultigraph = `graph {
   481  	// Node definitions.
   482  	0;
   483  	1;
   484  	2;
   485  
   486  	// Edge definitions.
   487  	0 -- 1;
   488  	0 -- 1;
   489  	0 -- 2;
   490  	0 -- 2;
   491  }`
   492  
   493  const directedSelfLoopMultigraph = `digraph {
   494  	// Node definitions.
   495  	0;
   496  	1;
   497  
   498  	// Edge definitions.
   499  	0 -> 0;
   500  	0 -> 0;
   501  	1 -> 1;
   502  	1 -> 1;
   503  }`
   504  
   505  const undirectedSelfLoopMultigraph = `graph {
   506  	// Node definitions.
   507  	0;
   508  	1;
   509  
   510  	// Edge definitions.
   511  	0 -- 0;
   512  	0 -- 0;
   513  	1 -- 1;
   514  	1 -- 1;
   515  }`
   516  
   517  // Below follows a minimal implementation of a graph capable of validating the
   518  // round-trip encoding and decoding of DOT graphs with nodes and edges
   519  // containing DOT attributes.
   520  
   521  // dotDirectedGraph extends simple.DirectedGraph to add NewNode and NewEdge
   522  // methods for creating user-defined nodes and edges.
   523  //
   524  // dotDirectedGraph implements the encoding.Builder and the dot.Graph
   525  // interfaces.
   526  type dotDirectedGraph struct {
   527  	*simple.DirectedGraph
   528  	id                string
   529  	graph, node, edge attributes
   530  }
   531  
   532  // newDotDirectedGraph returns a new directed capable of creating user-defined
   533  // nodes and edges.
   534  func newDotDirectedGraph() *dotDirectedGraph {
   535  	return &dotDirectedGraph{
   536  		DirectedGraph: simple.NewDirectedGraph(),
   537  
   538  		graph: &encoding.Attributes{},
   539  		node:  &encoding.Attributes{},
   540  		edge:  &encoding.Attributes{},
   541  	}
   542  }
   543  
   544  // NewNode returns a new node with a unique node ID for the graph.
   545  func (g *dotDirectedGraph) NewNode() graph.Node {
   546  	return &dotNode{Node: g.DirectedGraph.NewNode()}
   547  }
   548  
   549  // NewEdge returns a new Edge from the source to the destination node.
   550  func (g *dotDirectedGraph) NewEdge(from, to graph.Node) graph.Edge {
   551  	return &dotEdge{Edge: g.DirectedGraph.NewEdge(from, to)}
   552  }
   553  
   554  // DOTAttributers implements the dot.Attributers interface.
   555  func (g *dotDirectedGraph) DOTAttributers() (graph, node, edge encoding.Attributer) {
   556  	return g.graph, g.node, g.edge
   557  }
   558  
   559  // DOTAttributeSetters implements the dot.AttributeSetters interface.
   560  func (g *dotDirectedGraph) DOTAttributeSetters() (graph, node, edge encoding.AttributeSetter) {
   561  	return g.graph, g.node, g.edge
   562  }
   563  
   564  // SetDOTID sets the DOT ID of the graph.
   565  func (g *dotDirectedGraph) SetDOTID(id string) {
   566  	g.id = id
   567  }
   568  
   569  // DOTID returns the DOT ID of the graph.
   570  func (g *dotDirectedGraph) DOTID() string {
   571  	return g.id
   572  }
   573  
   574  // dotUndirectedGraph extends simple.UndirectedGraph to add NewNode and NewEdge
   575  // methods for creating user-defined nodes and edges.
   576  //
   577  // dotUndirectedGraph implements the encoding.Builder and the dot.Graph
   578  // interfaces.
   579  type dotUndirectedGraph struct {
   580  	*simple.UndirectedGraph
   581  	id                string
   582  	graph, node, edge attributes
   583  }
   584  
   585  // newDotUndirectedGraph returns a new undirected capable of creating user-
   586  // defined nodes and edges.
   587  func newDotUndirectedGraph() *dotUndirectedGraph {
   588  	return &dotUndirectedGraph{
   589  		UndirectedGraph: simple.NewUndirectedGraph(),
   590  
   591  		graph: &encoding.Attributes{},
   592  		node:  &encoding.Attributes{},
   593  		edge:  &encoding.Attributes{},
   594  	}
   595  }
   596  
   597  // NewNode adds a new node with a unique node ID to the graph.
   598  func (g *dotUndirectedGraph) NewNode() graph.Node {
   599  	return &dotNode{Node: g.UndirectedGraph.NewNode()}
   600  }
   601  
   602  // NewEdge returns a new Edge from the source to the destination node.
   603  func (g *dotUndirectedGraph) NewEdge(from, to graph.Node) graph.Edge {
   604  	return &dotEdge{Edge: g.UndirectedGraph.NewEdge(from, to)}
   605  }
   606  
   607  // DOTAttributers implements the dot.Attributers interface.
   608  func (g *dotUndirectedGraph) DOTAttributers() (graph, node, edge encoding.Attributer) {
   609  	return g.graph, g.node, g.edge
   610  }
   611  
   612  // DOTUnmarshalerAttrs implements the dot.UnmarshalerAttrs interface.
   613  func (g *dotUndirectedGraph) DOTAttributeSetters() (graph, node, edge encoding.AttributeSetter) {
   614  	return g.graph, g.node, g.edge
   615  }
   616  
   617  // SetDOTID sets the DOT ID of the graph.
   618  func (g *dotUndirectedGraph) SetDOTID(id string) {
   619  	g.id = id
   620  }
   621  
   622  // DOTID returns the DOT ID of the graph.
   623  func (g *dotUndirectedGraph) DOTID() string {
   624  	return g.id
   625  }
   626  
   627  // dotNode extends simple.Node with a label field to test round-trip encoding
   628  // and decoding of node DOT label attributes.
   629  type dotNode struct {
   630  	graph.Node
   631  	dotID string
   632  	// Node label.
   633  	Label string
   634  }
   635  
   636  // DOTID returns the node's DOT ID.
   637  func (n *dotNode) DOTID() string {
   638  	return n.dotID
   639  }
   640  
   641  // SetDOTID sets a DOT ID.
   642  func (n *dotNode) SetDOTID(id string) {
   643  	n.dotID = id
   644  }
   645  
   646  // SetAttribute sets a DOT attribute.
   647  func (n *dotNode) SetAttribute(attr encoding.Attribute) error {
   648  	if attr.Key != "label" {
   649  		return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key)
   650  	}
   651  	n.Label = attr.Value
   652  	return nil
   653  }
   654  
   655  // Attributes returns the DOT attributes of the node.
   656  func (n *dotNode) Attributes() []encoding.Attribute {
   657  	if len(n.Label) == 0 {
   658  		return nil
   659  	}
   660  	return []encoding.Attribute{{
   661  		Key:   "label",
   662  		Value: n.Label,
   663  	}}
   664  }
   665  
   666  type dotPortLabels struct {
   667  	Port, Compass string
   668  }
   669  
   670  // dotEdge extends simple.Edge with a label field to test round-trip encoding and
   671  // decoding of edge DOT label attributes.
   672  type dotEdge struct {
   673  	graph.Edge
   674  	// Edge label.
   675  	Label          string
   676  	FromPortLabels dotPortLabels
   677  	ToPortLabels   dotPortLabels
   678  }
   679  
   680  // SetAttribute sets a DOT attribute.
   681  func (e *dotEdge) SetAttribute(attr encoding.Attribute) error {
   682  	if attr.Key != "label" {
   683  		return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key)
   684  	}
   685  	e.Label = attr.Value
   686  	return nil
   687  }
   688  
   689  // Attributes returns the DOT attributes of the edge.
   690  func (e *dotEdge) Attributes() []encoding.Attribute {
   691  	if len(e.Label) == 0 {
   692  		return nil
   693  	}
   694  	return []encoding.Attribute{{
   695  		Key:   "label",
   696  		Value: e.Label,
   697  	}}
   698  }
   699  
   700  func (e *dotEdge) SetFromPort(port, compass string) error {
   701  	e.FromPortLabels.Port = port
   702  	e.FromPortLabels.Compass = compass
   703  	return nil
   704  }
   705  
   706  func (e *dotEdge) SetToPort(port, compass string) error {
   707  	e.ToPortLabels.Port = port
   708  	e.ToPortLabels.Compass = compass
   709  	return nil
   710  }
   711  
   712  func (e *dotEdge) FromPort() (port, compass string) {
   713  	return e.FromPortLabels.Port, e.FromPortLabels.Compass
   714  }
   715  
   716  func (e *dotEdge) ToPort() (port, compass string) {
   717  	return e.ToPortLabels.Port, e.ToPortLabels.Compass
   718  }
   719  
   720  type attributes interface {
   721  	encoding.Attributer
   722  	encoding.AttributeSetter
   723  }
   724  
   725  const undirectedSelfLoopGraph = `graph {
   726  	// Node definitions.
   727  	0;
   728  	1;
   729  
   730  	// Edge definitions.
   731  	0 -- 0;
   732  	1 -- 1;
   733  }`
   734  
   735  const directedSelfLoopGraph = `digraph {
   736  	// Node definitions.
   737  	0;
   738  	1;
   739  
   740  	// Edge definitions.
   741  	0 -> 0;
   742  	1 -> 1;
   743  }`
   744  
   745  func TestSelfLoopSimple(t *testing.T) {
   746  	for _, test := range []struct {
   747  		dst func() encoding.Builder
   748  		src string
   749  	}{
   750  		{
   751  			dst: func() encoding.Builder { return simple.NewUndirectedGraph() },
   752  			src: undirectedSelfLoopGraph,
   753  		},
   754  		{
   755  			dst: func() encoding.Builder { return simple.NewDirectedGraph() },
   756  			src: directedSelfLoopGraph,
   757  		},
   758  	} {
   759  		dst := test.dst()
   760  		message, panicked := panics(func() {
   761  			err := Unmarshal([]byte(test.src), dst)
   762  			if err == nil {
   763  				t.Errorf("expected error for self loop addition to %T", dst)
   764  			}
   765  		})
   766  		if panicked {
   767  			t.Errorf("unexpected panic for self loop addition to %T: %s", dst, message)
   768  		}
   769  	}
   770  }
   771  
   772  func panics(fn func()) (message string, ok bool) {
   773  	defer func() {
   774  		r := recover()
   775  		message = fmt.Sprint(r)
   776  		ok = r != nil
   777  	}()
   778  	fn()
   779  	return
   780  }