github.com/gopherd/gonum@v0.0.4/graph/graphs/gen/gen_test.go (about) 1 // Copyright ©2021 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 gen 6 7 import ( 8 "bytes" 9 "fmt" 10 "testing" 11 12 "github.com/gopherd/gonum/graph" 13 "github.com/gopherd/gonum/graph/encoding/dot" 14 "github.com/gopherd/gonum/graph/simple" 15 ) 16 17 type nodeIDGraphBuilder interface { 18 graph.Graph 19 NodeIDGraphBuilder 20 } 21 22 func undirected() nodeIDGraphBuilder { return simple.NewUndirectedGraph() } 23 func directed() nodeIDGraphBuilder { return simple.NewDirectedGraph() } 24 25 type empty struct{} 26 27 func (r empty) Len() int { return 0 } 28 func (r empty) ID(i int) int64 { panic("called ID on empty IDer") } 29 30 func panics(fn func()) (panicked bool, msg string) { 31 defer func() { 32 r := recover() 33 if r != nil { 34 panicked = true 35 msg = fmt.Sprint(r) 36 } 37 }() 38 fn() 39 return 40 } 41 42 func TestComplete(t *testing.T) { 43 tests := []struct { 44 name string 45 ids IDer 46 dst func() nodeIDGraphBuilder 47 want string 48 panics string 49 }{ 50 { 51 name: "empty", 52 ids: empty{}, 53 dst: undirected, 54 want: `strict graph empty { 55 }`, 56 }, 57 { 58 name: "single", 59 ids: IDRange{First: 1, Last: 1}, 60 dst: undirected, 61 want: `strict graph single { 62 // Node definitions. 63 1; 64 }`, 65 }, 66 { 67 name: "pair_undirected", 68 ids: IDRange{First: 1, Last: 2}, 69 dst: undirected, 70 want: `strict graph pair_undirected { 71 // Node definitions. 72 1; 73 2; 74 75 // Edge definitions. 76 1 -- 2; 77 }`, 78 }, 79 { 80 name: "pair_directed", 81 ids: IDRange{First: 1, Last: 2}, 82 dst: directed, 83 want: `strict digraph pair_directed { 84 // Node definitions. 85 1; 86 2; 87 88 // Edge definitions. 89 1 -> 2; 90 }`, 91 }, 92 { 93 name: "quad_undirected", 94 ids: IDRange{First: 1, Last: 4}, 95 dst: undirected, 96 want: `strict graph quad_undirected { 97 // Node definitions. 98 1; 99 2; 100 3; 101 4; 102 103 // Edge definitions. 104 1 -- 2; 105 1 -- 3; 106 1 -- 4; 107 2 -- 3; 108 2 -- 4; 109 3 -- 4; 110 }`, 111 }, 112 { 113 name: "quad_directed", 114 ids: IDRange{First: 1, Last: 4}, 115 dst: directed, 116 want: `strict digraph quad_directed { 117 // Node definitions. 118 1; 119 2; 120 3; 121 4; 122 123 // Edge definitions. 124 1 -> 2; 125 1 -> 3; 126 1 -> 4; 127 2 -> 3; 128 2 -> 4; 129 3 -> 4; 130 }`, 131 }, 132 { 133 name: "collision", 134 ids: IDSet{1, 2, 3, 2}, 135 dst: undirected, 136 panics: "gen: node ID collision i=1 j=3: id=2", 137 }, 138 } 139 140 for _, test := range tests { 141 dst := test.dst() 142 panicked, msg := panics(func() { Complete(dst, test.ids) }) 143 if msg != test.panics { 144 t.Errorf("unexpected panic message for %q: got:%q want:%q", test.name, msg, test.panics) 145 } 146 if panicked { 147 continue 148 } 149 got, err := dot.Marshal(dst, test.name, "", " ") 150 if err != nil { 151 t.Errorf("unexpected marshaling graph error: %v", err) 152 } 153 if !bytes.Equal(got, []byte(test.want)) { 154 t.Errorf("unexpected result for test %s:\ngot:\n%s\nwant:\n%s", test.name, got, test.want) 155 } 156 } 157 } 158 159 func TestCycle(t *testing.T) { 160 tests := []struct { 161 name string 162 ids IDer 163 dst func() nodeIDGraphBuilder 164 want string 165 panics string 166 }{ 167 { 168 name: "empty", 169 ids: empty{}, 170 dst: undirected, 171 want: `strict graph empty { 172 }`, 173 }, 174 { 175 name: "single", 176 ids: IDRange{First: 1, Last: 1}, 177 dst: undirected, 178 want: `strict graph single { 179 // Node definitions. 180 1; 181 }`, 182 }, 183 { 184 name: "pair_undirected", 185 ids: IDRange{First: 1, Last: 2}, 186 dst: undirected, 187 want: `strict graph pair_undirected { 188 // Node definitions. 189 1; 190 2; 191 192 // Edge definitions. 193 1 -- 2; 194 }`, 195 }, 196 { 197 name: "pair_directed", 198 ids: IDRange{First: 1, Last: 2}, 199 dst: directed, 200 want: `strict digraph pair_directed { 201 // Node definitions. 202 1; 203 2; 204 205 // Edge definitions. 206 1 -> 2; 207 2 -> 1; 208 }`, 209 }, 210 { 211 name: "quad_undirected", 212 ids: IDRange{First: 1, Last: 4}, 213 dst: undirected, 214 want: `strict graph quad_undirected { 215 // Node definitions. 216 1; 217 2; 218 3; 219 4; 220 221 // Edge definitions. 222 1 -- 2; 223 1 -- 4; 224 2 -- 3; 225 3 -- 4; 226 }`, 227 }, 228 { 229 name: "quad_directed", 230 ids: IDRange{First: 1, Last: 4}, 231 dst: directed, 232 want: `strict digraph quad_directed { 233 // Node definitions. 234 1; 235 2; 236 3; 237 4; 238 239 // Edge definitions. 240 1 -> 2; 241 2 -> 3; 242 3 -> 4; 243 4 -> 1; 244 }`, 245 }, 246 { 247 name: "collision", 248 ids: IDSet{1, 2, 3, 2}, 249 dst: undirected, 250 panics: "gen: node ID collision i=1 j=3: id=2", 251 }, 252 } 253 254 for _, test := range tests { 255 dst := test.dst() 256 panicked, msg := panics(func() { Cycle(dst, test.ids) }) 257 if msg != test.panics { 258 t.Errorf("unexpected panic message for %q: got:%q want:%q", test.name, msg, test.panics) 259 } 260 if panicked { 261 continue 262 } 263 got, err := dot.Marshal(dst, test.name, "", " ") 264 if err != nil { 265 t.Errorf("unexpected error marshaling graph: %v", err) 266 } 267 if !bytes.Equal(got, []byte(test.want)) { 268 t.Errorf("unexpected result for test %s:\ngot:\n%s\nwant:\n%s", test.name, got, test.want) 269 } 270 } 271 } 272 273 func TestPath(t *testing.T) { 274 tests := []struct { 275 name string 276 ids IDer 277 dst func() nodeIDGraphBuilder 278 want string 279 panics string 280 }{ 281 { 282 name: "empty", 283 ids: empty{}, 284 dst: undirected, 285 want: `strict graph empty { 286 }`, 287 }, 288 { 289 name: "single", 290 ids: IDRange{First: 1, Last: 1}, 291 dst: undirected, 292 want: `strict graph single { 293 // Node definitions. 294 1; 295 }`, 296 }, 297 { 298 name: "pair_undirected", 299 ids: IDRange{First: 1, Last: 2}, 300 dst: undirected, 301 want: `strict graph pair_undirected { 302 // Node definitions. 303 1; 304 2; 305 306 // Edge definitions. 307 1 -- 2; 308 }`, 309 }, 310 { 311 name: "pair_directed", 312 ids: IDRange{First: 1, Last: 2}, 313 dst: directed, 314 want: `strict digraph pair_directed { 315 // Node definitions. 316 1; 317 2; 318 319 // Edge definitions. 320 1 -> 2; 321 }`, 322 }, 323 { 324 name: "quad_undirected", 325 ids: IDRange{First: 1, Last: 4}, 326 dst: undirected, 327 want: `strict graph quad_undirected { 328 // Node definitions. 329 1; 330 2; 331 3; 332 4; 333 334 // Edge definitions. 335 1 -- 2; 336 2 -- 3; 337 3 -- 4; 338 }`, 339 }, 340 { 341 name: "quad_directed", 342 ids: IDRange{First: 1, Last: 4}, 343 dst: directed, 344 want: `strict digraph quad_directed { 345 // Node definitions. 346 1; 347 2; 348 3; 349 4; 350 351 // Edge definitions. 352 1 -> 2; 353 2 -> 3; 354 3 -> 4; 355 }`, 356 }, 357 { 358 name: "collision", 359 ids: IDSet{1, 2, 3, 2}, 360 dst: undirected, 361 panics: "gen: node ID collision i=1 j=3: id=2", 362 }, 363 } 364 365 for _, test := range tests { 366 dst := test.dst() 367 panicked, msg := panics(func() { Path(dst, test.ids) }) 368 if msg != test.panics { 369 t.Errorf("unexpected panic message for %q: got:%q want:%q", test.name, msg, test.panics) 370 } 371 if panicked { 372 continue 373 } 374 got, err := dot.Marshal(dst, test.name, "", " ") 375 if err != nil { 376 t.Errorf("unexpected error marshaling graph: %v", err) 377 } 378 if !bytes.Equal(got, []byte(test.want)) { 379 t.Errorf("unexpected result for test %s:\ngot:\n%s\nwant:\n%s", test.name, got, test.want) 380 } 381 } 382 } 383 384 func TestStar(t *testing.T) { 385 tests := []struct { 386 name string 387 center int64 388 leaves IDer 389 dst func() nodeIDGraphBuilder 390 want string 391 panics string 392 }{ 393 { 394 name: "empty_leaves", 395 center: 0, 396 leaves: empty{}, 397 dst: undirected, 398 want: `strict graph empty_leaves { 399 // Node definitions. 400 0; 401 }`, 402 }, 403 { 404 name: "single", 405 center: 0, 406 leaves: IDRange{First: 1, Last: 1}, 407 dst: undirected, 408 want: `strict graph single { 409 // Node definitions. 410 0; 411 1; 412 413 // Edge definitions. 414 0 -- 1; 415 }`, 416 }, 417 { 418 name: "pair_undirected", 419 center: 0, 420 leaves: IDRange{First: 1, Last: 2}, 421 dst: undirected, 422 want: `strict graph pair_undirected { 423 // Node definitions. 424 0; 425 1; 426 2; 427 428 // Edge definitions. 429 0 -- 1; 430 0 -- 2; 431 }`, 432 }, 433 { 434 name: "pair_directed", 435 center: 0, 436 leaves: IDRange{First: 1, Last: 2}, 437 dst: directed, 438 want: `strict digraph pair_directed { 439 // Node definitions. 440 0; 441 1; 442 2; 443 444 // Edge definitions. 445 0 -> 1; 446 0 -> 2; 447 }`, 448 }, 449 { 450 name: "quad_undirected", 451 center: 0, 452 leaves: IDRange{First: 1, Last: 4}, 453 dst: undirected, 454 want: `strict graph quad_undirected { 455 // Node definitions. 456 0; 457 1; 458 2; 459 3; 460 4; 461 462 // Edge definitions. 463 0 -- 1; 464 0 -- 2; 465 0 -- 3; 466 0 -- 4; 467 }`, 468 }, 469 { 470 name: "quad_directed", 471 center: 0, 472 leaves: IDRange{First: 1, Last: 4}, 473 dst: directed, 474 want: `strict digraph quad_directed { 475 // Node definitions. 476 0; 477 1; 478 2; 479 3; 480 4; 481 482 // Edge definitions. 483 0 -> 1; 484 0 -> 2; 485 0 -> 3; 486 0 -> 4; 487 }`, 488 }, 489 { 490 name: "center collision", 491 center: 1, 492 leaves: IDRange{First: 1, Last: 4}, 493 dst: undirected, 494 panics: "gen: node ID collision i=0 with extra: id=1", 495 }, 496 { 497 name: "leaf collision", 498 center: 0, 499 leaves: IDSet{1, 2, 3, 2}, 500 dst: undirected, 501 panics: "gen: node ID collision i=1 j=3: id=2", 502 }, 503 } 504 505 for _, test := range tests { 506 dst := test.dst() 507 panicked, msg := panics(func() { Star(dst, test.center, test.leaves) }) 508 if msg != test.panics { 509 t.Errorf("unexpected panic message for %q: got:%q want:%q", test.name, msg, test.panics) 510 } 511 if panicked { 512 continue 513 } 514 got, err := dot.Marshal(dst, test.name, "", " ") 515 if err != nil { 516 t.Errorf("unexpected error marshaling graph: %v", err) 517 } 518 if !bytes.Equal(got, []byte(test.want)) { 519 t.Errorf("unexpected result for test %s:\ngot:\n%s\nwant:\n%s", test.name, got, test.want) 520 } 521 } 522 } 523 524 func TestWheel(t *testing.T) { 525 tests := []struct { 526 name string 527 center int64 528 cycle IDer 529 dst func() nodeIDGraphBuilder 530 want string 531 panics string 532 }{ 533 { 534 name: "empty_cycle", 535 center: 0, 536 cycle: empty{}, 537 dst: undirected, 538 want: `strict graph empty_cycle { 539 // Node definitions. 540 0; 541 }`, 542 }, 543 { 544 name: "single", 545 cycle: IDRange{First: 1, Last: 1}, 546 dst: undirected, 547 want: `strict graph single { 548 // Node definitions. 549 0; 550 1; 551 552 // Edge definitions. 553 0 -- 1; 554 }`, 555 }, 556 { 557 name: "pair_undirected", 558 center: 0, 559 cycle: IDRange{First: 1, Last: 2}, 560 dst: undirected, 561 want: `strict graph pair_undirected { 562 // Node definitions. 563 0; 564 1; 565 2; 566 567 // Edge definitions. 568 0 -- 1; 569 0 -- 2; 570 1 -- 2; 571 }`, 572 }, 573 { 574 name: "pair_directed", 575 center: 0, 576 cycle: IDRange{First: 1, Last: 2}, 577 dst: directed, 578 want: `strict digraph pair_directed { 579 // Node definitions. 580 0; 581 1; 582 2; 583 584 // Edge definitions. 585 0 -> 1; 586 0 -> 2; 587 1 -> 2; 588 2 -> 1; 589 }`, 590 }, 591 { 592 name: "quad_undirected", 593 center: 0, 594 cycle: IDRange{First: 1, Last: 4}, 595 dst: undirected, 596 want: `strict graph quad_undirected { 597 // Node definitions. 598 0; 599 1; 600 2; 601 3; 602 4; 603 604 // Edge definitions. 605 0 -- 1; 606 0 -- 2; 607 0 -- 3; 608 0 -- 4; 609 1 -- 2; 610 1 -- 4; 611 2 -- 3; 612 3 -- 4; 613 }`, 614 }, 615 { 616 name: "quad_directed", 617 center: 0, 618 cycle: IDRange{First: 1, Last: 4}, 619 dst: directed, 620 want: `strict digraph quad_directed { 621 // Node definitions. 622 0; 623 1; 624 2; 625 3; 626 4; 627 628 // Edge definitions. 629 0 -> 1; 630 0 -> 2; 631 0 -> 3; 632 0 -> 4; 633 1 -> 2; 634 2 -> 3; 635 3 -> 4; 636 4 -> 1; 637 }`, 638 }, 639 { 640 name: "center collision", 641 center: 1, 642 cycle: IDRange{First: 1, Last: 4}, 643 dst: undirected, 644 panics: "gen: node ID collision i=0 with extra: id=1", 645 }, 646 { 647 name: "cycle collision", 648 center: 0, 649 cycle: IDSet{1, 2, 3, 2}, 650 dst: undirected, 651 panics: "gen: node ID collision i=1 j=3: id=2", 652 }, 653 } 654 655 for _, test := range tests { 656 dst := test.dst() 657 panicked, msg := panics(func() { Wheel(dst, test.center, test.cycle) }) 658 if msg != test.panics { 659 t.Errorf("unexpected panic message for %q: got:%q want:%q", test.name, msg, test.panics) 660 } 661 if panicked { 662 continue 663 } 664 got, err := dot.Marshal(dst, test.name, "", " ") 665 if err != nil { 666 t.Errorf("unexpected error marshaling graph: %v", err) 667 } 668 if !bytes.Equal(got, []byte(test.want)) { 669 t.Errorf("unexpected result for test %s:\ngot:\n%s\nwant:\n%s", test.name, got, test.want) 670 } 671 } 672 } 673 674 func TestCheck(t *testing.T) { 675 tests := []struct { 676 ids IDer 677 extra []int64 678 want string 679 }{ 680 { 681 ids: IDSet{1, 2, 3, 4}, extra: []int64{1}, 682 want: "gen: node ID collision i=0 with extra: id=1", 683 }, 684 { 685 ids: IDSet{1, 2, 3, 4}, extra: []int64{5, 2}, 686 want: "gen: node ID collision i=1 with extra j=1: id=2", 687 }, 688 { 689 ids: IDSet{}, extra: []int64{1, 2, 1}, 690 want: "gen: extra node ID collision i=0 j=2: id=1", 691 }, 692 } 693 694 for _, test := range tests { 695 msg := fmt.Sprint(check(test.ids, test.extra...)) 696 if msg != test.want { 697 t.Errorf("unexpected check panic for ids=%#v extra=%v: got:%q want:%q", 698 test.ids, test.extra, msg, test.want) 699 } 700 } 701 } 702 703 func TestTree(t *testing.T) { 704 tests := []struct { 705 name string 706 dst func() nodeIDGraphBuilder 707 nodes IDer 708 fanout int 709 want string 710 panics string 711 }{ 712 { 713 name: "empty_tree", 714 dst: undirected, 715 nodes: empty{}, 716 fanout: 2, 717 want: `strict graph empty_tree { 718 }`, 719 }, 720 { 721 name: "singleton_tree", 722 dst: undirected, 723 nodes: IDSet{0}, 724 fanout: 0, 725 want: `strict graph singleton_tree { 726 // Node definitions. 727 0; 728 }`, 729 }, 730 { 731 name: "full_binary_tree_undirected", 732 dst: undirected, 733 nodes: IDRange{First: 0, Last: 14}, 734 fanout: 2, 735 want: `strict graph full_binary_tree_undirected { 736 // Node definitions. 737 0; 738 1; 739 2; 740 3; 741 4; 742 5; 743 6; 744 7; 745 8; 746 9; 747 10; 748 11; 749 12; 750 13; 751 14; 752 753 // Edge definitions. 754 0 -- 1; 755 0 -- 2; 756 1 -- 3; 757 1 -- 4; 758 2 -- 5; 759 2 -- 6; 760 3 -- 7; 761 3 -- 8; 762 4 -- 9; 763 4 -- 10; 764 5 -- 11; 765 5 -- 12; 766 6 -- 13; 767 6 -- 14; 768 }`, 769 }, 770 { 771 name: "partial_ternary_tree_undirected", 772 dst: undirected, 773 nodes: IDRange{First: 0, Last: 17}, 774 fanout: 3, 775 want: `strict graph partial_ternary_tree_undirected { 776 // Node definitions. 777 0; 778 1; 779 2; 780 3; 781 4; 782 5; 783 6; 784 7; 785 8; 786 9; 787 10; 788 11; 789 12; 790 13; 791 14; 792 15; 793 16; 794 17; 795 796 // Edge definitions. 797 0 -- 1; 798 0 -- 2; 799 0 -- 3; 800 1 -- 4; 801 1 -- 5; 802 1 -- 6; 803 2 -- 7; 804 2 -- 8; 805 2 -- 9; 806 3 -- 10; 807 3 -- 11; 808 3 -- 12; 809 4 -- 13; 810 4 -- 14; 811 4 -- 15; 812 5 -- 16; 813 5 -- 17; 814 }`, 815 }, 816 { 817 name: "linear_graph_undirected", 818 dst: undirected, 819 nodes: IDRange{First: 0, Last: 4}, 820 fanout: 1, 821 want: `strict graph linear_graph_undirected { 822 // Node definitions. 823 0; 824 1; 825 2; 826 3; 827 4; 828 829 // Edge definitions. 830 0 -- 1; 831 1 -- 2; 832 2 -- 3; 833 3 -- 4; 834 }`, 835 }, 836 { 837 name: "full_ternary_tree_directed", 838 dst: directed, 839 nodes: IDRange{First: 0, Last: 12}, 840 fanout: 3, 841 want: `strict digraph full_ternary_tree_directed { 842 // Node definitions. 843 0; 844 1; 845 2; 846 3; 847 4; 848 5; 849 6; 850 7; 851 8; 852 9; 853 10; 854 11; 855 12; 856 857 // Edge definitions. 858 0 -> 1; 859 0 -> 2; 860 0 -> 3; 861 1 -> 4; 862 1 -> 5; 863 1 -> 6; 864 2 -> 7; 865 2 -> 8; 866 2 -> 9; 867 3 -> 10; 868 3 -> 11; 869 3 -> 12; 870 }`, 871 }, 872 { 873 name: "bad_fanout", 874 dst: undirected, 875 nodes: IDSet{0, 1, 2, 3}, 876 fanout: -1, 877 panics: "gen: invalid fan-out", 878 }, 879 { 880 name: "collision", 881 dst: undirected, 882 nodes: IDSet{0, 1, 2, 3, 2}, 883 fanout: 2, 884 panics: "gen: node ID collision i=2 j=4: id=2", 885 }, 886 } 887 for _, test := range tests { 888 dst := test.dst() 889 panicked, msg := panics(func() { Tree(dst, test.fanout, test.nodes) }) 890 if msg != test.panics { 891 t.Errorf("unexpected panic message for %q: got:%q want:%q", test.name, msg, test.panics) 892 } 893 if panicked { 894 continue 895 } 896 got, err := dot.Marshal(dst, test.name, "", " ") 897 if err != nil { 898 t.Errorf("unexpected error marshaling graph: %v", err) 899 } 900 if !bytes.Equal(got, []byte(test.want)) { 901 t.Errorf("unexpected result for test %s:\ngot:\n%s\nwant:\n%s", test.name, got, test.want) 902 } 903 } 904 }