github.com/jingcheng-WU/gonum@v0.9.1-0.20210323123734-f1a2a11a8f7b/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 "github.com/jingcheng-WU/gonum/graph" 12 "github.com/jingcheng-WU/gonum/graph/encoding" 13 "github.com/jingcheng-WU/gonum/graph/multi" 14 "github.com/jingcheng-WU/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{DirectedGraph: simple.NewDirectedGraph()} 536 } 537 538 // NewNode returns a new node with a unique node ID for the graph. 539 func (g *dotDirectedGraph) NewNode() graph.Node { 540 return &dotNode{Node: g.DirectedGraph.NewNode()} 541 } 542 543 // NewEdge returns a new Edge from the source to the destination node. 544 func (g *dotDirectedGraph) NewEdge(from, to graph.Node) graph.Edge { 545 return &dotEdge{Edge: g.DirectedGraph.NewEdge(from, to)} 546 } 547 548 // DOTAttributers implements the dot.Attributers interface. 549 func (g *dotDirectedGraph) DOTAttributers() (graph, node, edge encoding.Attributer) { 550 return g.graph, g.node, g.edge 551 } 552 553 // DOTAttributeSetters implements the dot.AttributeSetters interface. 554 func (g *dotDirectedGraph) DOTAttributeSetters() (graph, node, edge encoding.AttributeSetter) { 555 return &g.graph, &g.node, &g.edge 556 } 557 558 // SetDOTID sets the DOT ID of the graph. 559 func (g *dotDirectedGraph) SetDOTID(id string) { 560 g.id = id 561 } 562 563 // DOTID returns the DOT ID of the graph. 564 func (g *dotDirectedGraph) DOTID() string { 565 return g.id 566 } 567 568 // dotUndirectedGraph extends simple.UndirectedGraph to add NewNode and NewEdge 569 // methods for creating user-defined nodes and edges. 570 // 571 // dotUndirectedGraph implements the encoding.Builder and the dot.Graph 572 // interfaces. 573 type dotUndirectedGraph struct { 574 *simple.UndirectedGraph 575 id string 576 graph, node, edge attributes 577 } 578 579 // newDotUndirectedGraph returns a new undirected capable of creating user- 580 // defined nodes and edges. 581 func newDotUndirectedGraph() *dotUndirectedGraph { 582 return &dotUndirectedGraph{UndirectedGraph: simple.NewUndirectedGraph()} 583 } 584 585 // NewNode adds a new node with a unique node ID to the graph. 586 func (g *dotUndirectedGraph) NewNode() graph.Node { 587 return &dotNode{Node: g.UndirectedGraph.NewNode()} 588 } 589 590 // NewEdge returns a new Edge from the source to the destination node. 591 func (g *dotUndirectedGraph) NewEdge(from, to graph.Node) graph.Edge { 592 return &dotEdge{Edge: g.UndirectedGraph.NewEdge(from, to)} 593 } 594 595 // DOTAttributers implements the dot.Attributers interface. 596 func (g *dotUndirectedGraph) DOTAttributers() (graph, node, edge encoding.Attributer) { 597 return g.graph, g.node, g.edge 598 } 599 600 // DOTUnmarshalerAttrs implements the dot.UnmarshalerAttrs interface. 601 func (g *dotUndirectedGraph) DOTAttributeSetters() (graph, node, edge encoding.AttributeSetter) { 602 return &g.graph, &g.node, &g.edge 603 } 604 605 // SetDOTID sets the DOT ID of the graph. 606 func (g *dotUndirectedGraph) SetDOTID(id string) { 607 g.id = id 608 } 609 610 // DOTID returns the DOT ID of the graph. 611 func (g *dotUndirectedGraph) DOTID() string { 612 return g.id 613 } 614 615 // dotNode extends simple.Node with a label field to test round-trip encoding 616 // and decoding of node DOT label attributes. 617 type dotNode struct { 618 graph.Node 619 dotID string 620 // Node label. 621 Label string 622 } 623 624 // DOTID returns the node's DOT ID. 625 func (n *dotNode) DOTID() string { 626 return n.dotID 627 } 628 629 // SetDOTID sets a DOT ID. 630 func (n *dotNode) SetDOTID(id string) { 631 n.dotID = id 632 } 633 634 // SetAttribute sets a DOT attribute. 635 func (n *dotNode) SetAttribute(attr encoding.Attribute) error { 636 if attr.Key != "label" { 637 return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key) 638 } 639 n.Label = attr.Value 640 return nil 641 } 642 643 // Attributes returns the DOT attributes of the node. 644 func (n *dotNode) Attributes() []encoding.Attribute { 645 if len(n.Label) == 0 { 646 return nil 647 } 648 return []encoding.Attribute{{ 649 Key: "label", 650 Value: n.Label, 651 }} 652 } 653 654 type dotPortLabels struct { 655 Port, Compass string 656 } 657 658 // dotEdge extends simple.Edge with a label field to test round-trip encoding and 659 // decoding of edge DOT label attributes. 660 type dotEdge struct { 661 graph.Edge 662 // Edge label. 663 Label string 664 FromPortLabels dotPortLabels 665 ToPortLabels dotPortLabels 666 } 667 668 // SetAttribute sets a DOT attribute. 669 func (e *dotEdge) SetAttribute(attr encoding.Attribute) error { 670 if attr.Key != "label" { 671 return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key) 672 } 673 e.Label = attr.Value 674 return nil 675 } 676 677 // Attributes returns the DOT attributes of the edge. 678 func (e *dotEdge) Attributes() []encoding.Attribute { 679 if len(e.Label) == 0 { 680 return nil 681 } 682 return []encoding.Attribute{{ 683 Key: "label", 684 Value: e.Label, 685 }} 686 } 687 688 func (e *dotEdge) SetFromPort(port, compass string) error { 689 e.FromPortLabels.Port = port 690 e.FromPortLabels.Compass = compass 691 return nil 692 } 693 694 func (e *dotEdge) SetToPort(port, compass string) error { 695 e.ToPortLabels.Port = port 696 e.ToPortLabels.Compass = compass 697 return nil 698 } 699 700 func (e *dotEdge) FromPort() (port, compass string) { 701 return e.FromPortLabels.Port, e.FromPortLabels.Compass 702 } 703 704 func (e *dotEdge) ToPort() (port, compass string) { 705 return e.ToPortLabels.Port, e.ToPortLabels.Compass 706 } 707 708 // attributes is a helper for global attributes. 709 type attributes []encoding.Attribute 710 711 func (a attributes) Attributes() []encoding.Attribute { 712 return []encoding.Attribute(a) 713 } 714 func (a *attributes) SetAttribute(attr encoding.Attribute) error { 715 *a = append(*a, attr) 716 return nil 717 } 718 719 const undirectedSelfLoopGraph = `graph { 720 // Node definitions. 721 0; 722 1; 723 724 // Edge definitions. 725 0 -- 0; 726 1 -- 1; 727 }` 728 729 const directedSelfLoopGraph = `digraph { 730 // Node definitions. 731 0; 732 1; 733 734 // Edge definitions. 735 0 -> 0; 736 1 -> 1; 737 }` 738 739 func TestSelfLoopSimple(t *testing.T) { 740 for _, test := range []struct { 741 dst func() encoding.Builder 742 src string 743 }{ 744 { 745 dst: func() encoding.Builder { return simple.NewUndirectedGraph() }, 746 src: undirectedSelfLoopGraph, 747 }, 748 { 749 dst: func() encoding.Builder { return simple.NewDirectedGraph() }, 750 src: directedSelfLoopGraph, 751 }, 752 } { 753 dst := test.dst() 754 message, panicked := panics(func() { 755 err := Unmarshal([]byte(test.src), dst) 756 if err == nil { 757 t.Errorf("expected error for self loop addition to %T", dst) 758 } 759 }) 760 if panicked { 761 t.Errorf("unexpected panic for self loop addition to %T: %s", dst, message) 762 } 763 } 764 } 765 766 func panics(fn func()) (message string, ok bool) { 767 defer func() { 768 r := recover() 769 message = fmt.Sprint(r) 770 ok = r != nil 771 }() 772 fn() 773 return 774 }