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 }