github.com/openconfig/goyang@v1.4.5/pkg/yang/types_test.go (about) 1 // Copyright 2015 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package yang 16 17 import ( 18 "fmt" 19 "testing" 20 21 "github.com/google/go-cmp/cmp" 22 "github.com/google/go-cmp/cmp/cmpopts" 23 "github.com/openconfig/gnmi/errdiff" 24 ) 25 26 func TestTypeResolve(t *testing.T) { 27 tests := []struct { 28 desc string 29 in *Type 30 err string 31 out *YangType 32 }{{ 33 desc: "basic int64", 34 in: &Type{ 35 Name: "int64", 36 }, 37 out: &YangType{ 38 Name: "int64", 39 Kind: Yint64, 40 Range: Int64Range, 41 }, 42 }, { 43 desc: "basic int64 with a range", 44 in: &Type{ 45 Name: "int64", 46 Range: &Range{Name: "-42..42"}, 47 }, 48 out: &YangType{ 49 Name: "int64", 50 Kind: Yint64, 51 Range: YangRange{{Min: FromInt(-42), Max: FromInt(42)}}, 52 }, 53 }, { 54 desc: "basic uint64 with an invalid range", 55 in: &Type{ 56 Name: "uint64", 57 Range: &Range{Name: "-42..42"}, 58 }, 59 err: "unknown: bad range: -42..42 not within 0..18446744073709551615", 60 }, { 61 desc: "basic uint64 with an unparseable range", 62 in: &Type{ 63 Name: "uint64", 64 Range: &Range{Name: "-42..forty-two"}, 65 }, 66 err: `unknown: bad range: strconv.ParseUint: parsing "forty-two": invalid syntax`, 67 }, { 68 desc: "basic string with a length", 69 in: &Type{ 70 Name: "string", 71 Length: &Length{Name: "24..42"}, 72 }, 73 out: &YangType{ 74 Name: "string", 75 Kind: Ystring, 76 Length: YangRange{{Min: FromInt(24), Max: FromInt(42)}}, 77 }, 78 }, { 79 desc: "basic string with an invalid range", 80 in: &Type{ 81 Name: "string", 82 Length: &Length{Name: "-42..42"}, 83 }, 84 err: `unknown: bad length: -42..42 not within 0..18446744073709551615`, 85 }, { 86 desc: "basic binary with a length", 87 in: &Type{ 88 Name: "binary", 89 Length: &Length{Name: "24..42"}, 90 }, 91 out: &YangType{ 92 Name: "binary", 93 Kind: Ybinary, 94 Length: YangRange{{Min: FromInt(24), Max: FromInt(42)}}, 95 }, 96 }, { 97 desc: "basic binary with an unparseable range", 98 in: &Type{ 99 Name: "binary", 100 Length: &Length{Name: "42..forty-two"}, 101 }, 102 err: `unknown: bad length: strconv.ParseUint: parsing "forty-two": invalid syntax`, 103 }, { 104 desc: "invalid fraction-digits argument for boolean value", 105 in: &Type{ 106 Name: "boolean", 107 FractionDigits: &Value{Name: "42"}, 108 }, 109 err: "unknown: fraction-digits only allowed for decimal64 values", 110 }, { 111 desc: "required field fraction-digits not supplied for decimal64", 112 in: &Type{ 113 Name: "decimal64", 114 }, 115 err: "unknown: value is required in the range of [1..18]", 116 }, { 117 desc: "invalid identityref that doesn't have a base identity name", 118 in: &Type{ 119 Name: "identityref", 120 }, 121 err: "unknown: an identityref must specify a base", 122 }, { 123 desc: "invalid decimal64 having an invalid fraction-digits value", 124 in: &Type{ 125 Name: "decimal64", 126 FractionDigits: &Value{Name: "42"}, 127 }, 128 err: "unknown: value 42 out of range [1..18]", 129 }, { 130 desc: "decimal64", 131 in: &Type{ 132 Name: "decimal64", 133 FractionDigits: &Value{Name: "7"}, 134 }, 135 out: &YangType{ 136 Name: "decimal64", 137 Kind: Ydecimal64, 138 FractionDigits: 7, 139 Range: YangRange{Rf(MinInt64, MaxInt64, 7)}, 140 }, 141 }, { 142 desc: "instance-identifier with unspecified require-instance value (default true)", 143 in: &Type{ 144 Name: "instance-identifier", 145 RequireInstance: nil, 146 }, 147 out: &YangType{ 148 Name: "instance-identifier", 149 Kind: YinstanceIdentifier, 150 // https://tools.ietf.org/html/rfc7950#section-9.9.3 151 // require-instance defaults to true. 152 OptionalInstance: false, 153 }, 154 }, { 155 desc: "instance-identifier with true require-instance value", 156 in: &Type{ 157 Name: "instance-identifier", 158 RequireInstance: &Value{Name: "true"}, 159 }, 160 out: &YangType{ 161 Name: "instance-identifier", 162 Kind: YinstanceIdentifier, 163 OptionalInstance: false, 164 }, 165 }, { 166 desc: "instance-identifier with false require-instance value", 167 in: &Type{ 168 Name: "instance-identifier", 169 RequireInstance: &Value{Name: "false"}, 170 }, 171 out: &YangType{ 172 Name: "instance-identifier", 173 Kind: YinstanceIdentifier, 174 OptionalInstance: true, 175 }, 176 }, { 177 desc: "instance-identifier with invalid require-instance value", 178 in: &Type{ 179 Name: "instance-identifier", 180 RequireInstance: &Value{Name: "foo"}, 181 }, 182 err: "invalid boolean: foo", 183 }, { 184 desc: "enum with unspecified values", 185 in: &Type{ 186 Name: "enumeration", 187 Enum: []*Enum{ 188 {Name: "MERCURY"}, 189 {Name: "VENUS"}, 190 {Name: "EARTH"}, 191 }, 192 }, 193 out: &YangType{ 194 Name: "enumeration", 195 Kind: Yenum, 196 Enum: &EnumType{ 197 last: 2, 198 min: MinEnum, 199 max: MaxEnum, 200 unique: true, 201 ToString: map[int64]string{ 202 0: "MERCURY", 203 1: "VENUS", 204 2: "EARTH", 205 }, 206 ToInt: map[string]int64{ 207 "MERCURY": 0, 208 "VENUS": 1, 209 "EARTH": 2, 210 }, 211 }, 212 }, 213 }, { 214 desc: "enum with specified values", 215 in: &Type{ 216 Name: "enumeration", 217 Enum: []*Enum{ 218 {Name: "MERCURY", Value: &Value{Name: "-1"}}, 219 {Name: "VENUS", Value: &Value{Name: "10"}}, 220 {Name: "EARTH", Value: &Value{Name: "30"}}, 221 }, 222 }, 223 out: &YangType{ 224 Name: "enumeration", 225 Kind: Yenum, 226 Enum: &EnumType{ 227 last: 30, 228 min: MinEnum, 229 max: MaxEnum, 230 unique: true, 231 ToString: map[int64]string{ 232 -1: "MERCURY", 233 10: "VENUS", 234 30: "EARTH", 235 }, 236 ToInt: map[string]int64{ 237 "MERCURY": -1, 238 "VENUS": 10, 239 "EARTH": 30, 240 }, 241 }, 242 }, 243 }, { 244 desc: "enum with some values specified", 245 in: &Type{ 246 Name: "enumeration", 247 Enum: []*Enum{ 248 {Name: "MERCURY", Value: &Value{Name: "-1"}}, 249 {Name: "VENUS", Value: &Value{Name: "10"}}, 250 {Name: "EARTH"}, 251 }, 252 }, 253 out: &YangType{ 254 Name: "enumeration", 255 Kind: Yenum, 256 Enum: &EnumType{ 257 last: 11, 258 min: MinEnum, 259 max: MaxEnum, 260 unique: true, 261 ToString: map[int64]string{ 262 -1: "MERCURY", 263 10: "VENUS", 264 11: "EARTH", 265 }, 266 ToInt: map[string]int64{ 267 "MERCURY": -1, 268 "VENUS": 10, 269 "EARTH": 11, 270 }, 271 }, 272 }, 273 }, { 274 desc: "enum with repeated specified values", 275 in: &Type{ 276 Name: "enumeration", 277 Enum: []*Enum{ 278 {Name: "MERCURY", Value: &Value{Name: "1"}}, 279 {Name: "VENUS", Value: &Value{Name: "10"}}, 280 {Name: "EARTH", Value: &Value{Name: "1"}}, 281 }, 282 }, 283 err: "unknown: fields EARTH and MERCURY conflict on value 1", 284 }, { 285 desc: "enum with repeated specified names", 286 in: &Type{ 287 Name: "enumeration", 288 Enum: []*Enum{ 289 {Name: "MERCURY", Value: &Value{Name: "-1"}}, 290 {Name: "VENUS", Value: &Value{Name: "10"}}, 291 {Name: "MERCURY", Value: &Value{Name: "30"}}, 292 }, 293 }, 294 err: "unknown: field MERCURY already assigned", 295 }, { 296 desc: "enum with last specified value equal to the max enum value", 297 in: &Type{ 298 Name: "enumeration", 299 Enum: []*Enum{ 300 {Name: "MERCURY", Value: &Value{Name: "-2147483648"}}, 301 {Name: "VENUS", Value: &Value{Name: "2147483647"}}, 302 {Name: "EARTH"}, 303 }, 304 }, 305 err: `unknown: enum "EARTH" must specify a value since previous enum is the maximum value allowed`, 306 }, { 307 desc: "enum value too small", 308 in: &Type{ 309 Name: "enumeration", 310 Enum: []*Enum{ 311 {Name: "MERCURY", Value: &Value{Name: "-2147483649"}}, 312 {Name: "VENUS", Value: &Value{Name: "0"}}, 313 {Name: "EARTH"}, 314 }, 315 }, 316 err: `unknown: value -2147483649 for MERCURY too small (minimum is -2147483648)`, 317 }, { 318 desc: "enum value too large", 319 in: &Type{ 320 Name: "enumeration", 321 Enum: []*Enum{ 322 {Name: "MERCURY", Value: &Value{Name: "-2147483648"}}, 323 {Name: "VENUS", Value: &Value{Name: "2147483648"}}, 324 {Name: "EARTH"}, 325 }, 326 }, 327 err: `unknown: value 2147483648 for VENUS too large (maximum is 2147483647)`, 328 }, { 329 desc: "enum with an unparseable value", 330 in: &Type{ 331 Name: "enumeration", 332 Enum: []*Enum{ 333 {Name: "MERCURY", Value: &Value{Name: "-1"}}, 334 {Name: "VENUS", Value: &Value{Name: "10"}}, 335 {Name: "EARTH", Value: &Value{Name: "five"}}, 336 }, 337 }, 338 err: `unknown: strconv.ParseUint: parsing "five": invalid syntax`, 339 // TODO(borman): Add in more tests as we honor more fields 340 // in Type. 341 }} 342 343 for _, tt := range tests { 344 t.Run(tt.desc, func(t *testing.T) { 345 // We can initialize a value to ourself, so to it here. 346 errs := tt.in.resolve(newTypeDictionary()) 347 348 // TODO(borman): Do not hack out Root and Base. These 349 // are hacked out for now because they can be self-referential, 350 // making construction of them difficult. 351 tt.in.YangType.Root = nil 352 tt.in.YangType.Base = nil 353 354 switch { 355 case tt.err == "" && len(errs) > 0: 356 t.Fatalf("unexpected errors: %v", errs) 357 case tt.err != "" && len(errs) == 0: 358 t.Fatalf("did not get expected errors: %v", tt.err) 359 case len(errs) > 1: 360 t.Fatalf("too many errors: %v", errs) 361 case len(errs) == 1 && errs[0].Error() != tt.err: 362 t.Fatalf("got error %v, want %s", errs[0], tt.err) 363 case len(errs) != 0: 364 return 365 } 366 367 if diff := cmp.Diff(tt.in.YangType, tt.out); diff != "" { 368 t.Errorf("YangType (-got, +want):\n%s", diff) 369 } 370 }) 371 } 372 } 373 374 func TestTypedefResolve(t *testing.T) { 375 tests := []struct { 376 desc string 377 in *Typedef 378 err string 379 out *YangType 380 }{{ 381 desc: "basic int64", 382 in: &Typedef{ 383 Name: "time", 384 Parent: baseTypes["int64"].typedef(), 385 Default: &Value{Name: "42"}, 386 Type: &Type{ 387 Name: "int64", 388 }, 389 Units: &Value{Name: "nanoseconds"}, 390 }, 391 out: &YangType{ 392 Name: "time", 393 Kind: Yint64, 394 Base: &Type{ 395 Name: "int64", 396 }, 397 Units: "nanoseconds", 398 Default: "42", 399 HasDefault: true, 400 Range: Int64Range, 401 }, 402 }, { 403 desc: "uint32 with more specific range", 404 in: &Typedef{ 405 Name: "another-counter", 406 Parent: &Typedef{ 407 Name: "counter", 408 Parent: baseTypes["uint32"].typedef(), 409 Type: &Type{ 410 Name: "uint32", 411 Range: &Range{Name: "0..42"}, 412 }, 413 }, 414 Type: &Type{ 415 Name: "uint32", 416 Range: &Range{Name: "10..20"}, 417 }, 418 }, 419 out: &YangType{ 420 Name: "another-counter", 421 Kind: Yuint32, 422 Base: &Type{ 423 Name: "uint32", 424 }, 425 Range: YangRange{{Min: FromInt(10), Max: FromInt(20)}}, 426 }, 427 // TODO(wenovus): Add tests on range and length inheritance once those are fixed. 428 }} 429 430 for _, tt := range tests { 431 t.Run(tt.desc, func(t *testing.T) { 432 // We can initialize a value to ourself, so to it here. 433 errs := tt.in.resolve(newTypeDictionary()) 434 435 switch { 436 case tt.err == "" && len(errs) > 0: 437 t.Fatalf("unexpected errors: %v", errs) 438 case tt.err != "" && len(errs) == 0: 439 t.Fatalf("did not get expected errors: %v", tt.err) 440 case len(errs) > 1: 441 t.Fatalf("too many errors: %v", errs) 442 case len(errs) == 1 && errs[0].Error() != tt.err: 443 t.Fatalf("got error %v, want %s", errs[0], tt.err) 444 case len(errs) != 0: 445 return 446 } 447 448 if diff := cmp.Diff(tt.in.YangType, tt.out); diff != "" { 449 t.Errorf("YangType (-got, +want):\n%s", diff) 450 } 451 }) 452 } 453 } 454 455 func TestTypeResolveUnions(t *testing.T) { 456 tests := []struct { 457 desc string 458 leafNode string 459 wantType *testEnumTypeStruct 460 wantErrSubstr string 461 }{{ 462 desc: "simple union", 463 leafNode: ` 464 typedef alpha { 465 type union { 466 type string; 467 type uint32; 468 type enumeration { 469 enum zero; 470 enum one; 471 enum seven { 472 value 7; 473 } 474 } 475 } 476 } 477 478 leaf test-leaf { 479 type alpha; 480 } 481 } // end module`, 482 wantType: &testEnumTypeStruct{ 483 Name: "alpha", 484 Type: []*testEnumTypeStruct{{ 485 Name: "string", 486 }, { 487 Name: "uint32", 488 }, { 489 Name: "enumeration", 490 ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0}, 491 }}, 492 }, 493 }, { 494 desc: "union with typedef", 495 leafNode: ` 496 typedef alpha { 497 type union { 498 type string; 499 type uint32; 500 type enumeration { 501 enum zero; 502 enum one; 503 enum seven { 504 value 7; 505 } 506 } 507 type bravo; 508 } 509 } 510 511 typedef bravo { 512 type union { 513 type uint8; 514 type uint16; 515 type enumeration { 516 enum two { 517 value 2; 518 } 519 enum three; 520 enum four; 521 } 522 } 523 } 524 525 leaf test-leaf { 526 type alpha; 527 } 528 } // end module`, 529 wantType: &testEnumTypeStruct{ 530 Name: "alpha", 531 Type: []*testEnumTypeStruct{{ 532 Name: "string", 533 }, { 534 Name: "uint32", 535 }, { 536 Name: "enumeration", 537 ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0}, 538 }, { 539 Name: "bravo", 540 Type: []*testEnumTypeStruct{{ 541 Name: "uint8", 542 }, { 543 Name: "uint16", 544 }, { 545 Name: "enumeration", 546 ToInt: map[string]int64{"two": 2, "three": 3, "four": 4}, 547 }}, 548 }}, 549 }, 550 }, { 551 desc: "nested unions with typedef", 552 leafNode: ` 553 typedef alpha { 554 type union { 555 type union { 556 type uint32; 557 type string; 558 type enumeration { 559 enum zero; 560 enum one; 561 enum seven { 562 value 7; 563 } 564 } 565 } 566 type bravo; 567 } 568 } 569 570 typedef bravo { 571 type union { 572 type uint8; 573 type uint16; 574 type enumeration { 575 enum two { 576 value 2; 577 } 578 enum three; 579 enum four; 580 } 581 } 582 } 583 584 leaf test-leaf { 585 type alpha; 586 } 587 } // end module`, 588 wantType: &testEnumTypeStruct{ 589 Name: "alpha", 590 Type: []*testEnumTypeStruct{{ 591 Name: "union", 592 Type: []*testEnumTypeStruct{{ 593 Name: "uint32", 594 }, { 595 Name: "string", 596 }, { 597 Name: "enumeration", 598 ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0}, 599 }}, 600 }, { 601 Name: "bravo", 602 Type: []*testEnumTypeStruct{{ 603 Name: "uint8", 604 }, { 605 Name: "uint16", 606 }, { 607 Name: "enumeration", 608 ToInt: map[string]int64{"two": 2, "three": 3, "four": 4}, 609 }}, 610 }}, 611 }, 612 }, { 613 desc: "simple union with multiple enumerations", 614 leafNode: ` 615 leaf test-leaf { 616 type union { 617 type string; 618 type uint32; 619 type enumeration { 620 enum zero; 621 enum one; 622 enum seven { 623 value 7; 624 } 625 } 626 type enumeration { 627 enum two { 628 value 2; 629 } 630 enum three; 631 enum four; 632 } 633 } 634 } 635 } // end module`, 636 wantType: &testEnumTypeStruct{ 637 Name: "union", 638 Type: []*testEnumTypeStruct{{ 639 Name: "string", 640 }, { 641 Name: "uint32", 642 }, { 643 Name: "enumeration", 644 ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0}, 645 }, { 646 Name: "enumeration", 647 ToInt: map[string]int64{"two": 2, "three": 3, "four": 4}, 648 }}, 649 }, 650 }, { 651 desc: "typedef union with multiple enumerations", 652 leafNode: ` 653 typedef alpha { 654 type union { 655 type string; 656 type uint32; 657 type enumeration { 658 enum zero; 659 enum one; 660 enum seven { 661 value 7; 662 } 663 } 664 type enumeration { 665 enum two { 666 value 2; 667 } 668 enum three; 669 enum four; 670 } 671 } 672 } 673 674 leaf test-leaf { 675 type alpha; 676 } 677 } // end module`, 678 wantType: &testEnumTypeStruct{ 679 Name: "alpha", 680 Type: []*testEnumTypeStruct{{ 681 Name: "string", 682 }, { 683 Name: "uint32", 684 }, { 685 Name: "enumeration", 686 ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0}, 687 }, { 688 Name: "enumeration", 689 ToInt: map[string]int64{"two": 2, "three": 3, "four": 4}, 690 }}, 691 }, 692 }, { 693 desc: "simple union containing typedef union, both with enumerations", 694 leafNode: ` 695 typedef alpha { 696 type union { 697 type string; 698 type uint32; 699 type enumeration { 700 enum zero; 701 enum one; 702 enum seven { 703 value 7; 704 } 705 } 706 } 707 } 708 709 leaf test-leaf { 710 type union { 711 type alpha; 712 type enumeration { 713 enum two { 714 value 2; 715 } 716 enum three; 717 enum four; 718 } 719 } 720 } 721 } // end module`, 722 wantType: &testEnumTypeStruct{ 723 Name: "union", 724 Type: []*testEnumTypeStruct{{ 725 Name: "alpha", 726 Type: []*testEnumTypeStruct{{ 727 Name: "string", 728 }, { 729 Name: "uint32", 730 }, { 731 Name: "enumeration", 732 ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0}, 733 }}, 734 }, { 735 Name: "enumeration", 736 ToInt: map[string]int64{"two": 2, "three": 3, "four": 4}, 737 }}, 738 }, 739 }, { 740 desc: "simple union containing typedef union containing another typedef union, all with multiple simple and typedef enumerations", 741 leafNode: ` 742 typedef a { 743 type enumeration { 744 enum un { 745 value 1; 746 } 747 enum deux; 748 } 749 } 750 751 typedef b { 752 type enumeration { 753 enum trois { 754 value 3; 755 } 756 enum quatre; 757 } 758 } 759 760 typedef c { 761 type enumeration { 762 enum cinq { 763 value 5; 764 } 765 enum sept { 766 value 7; 767 } 768 } 769 } 770 771 typedef d { 772 type enumeration { 773 enum huit { 774 value 8; 775 } 776 enum neuf; 777 } 778 } 779 780 typedef e { 781 type enumeration { 782 enum dix { 783 value 10; 784 } 785 enum onze; 786 } 787 } 788 789 typedef f { 790 type enumeration { 791 enum douze { 792 value 12; 793 } 794 enum treize; 795 } 796 } 797 798 typedef bravo { 799 type union { 800 type uint32; 801 type enumeration { 802 enum eight { 803 value 8; 804 } 805 enum nine; 806 } 807 type enumeration { 808 enum ten { 809 value 10; 810 } 811 enum eleven; 812 } 813 type e; 814 type f; 815 } 816 } 817 818 typedef alpha { 819 type union { 820 type uint16; 821 type enumeration { 822 enum four { 823 value 4; 824 } 825 enum five; 826 } 827 type enumeration { 828 enum six { 829 value 6; 830 } 831 enum seven; 832 } 833 type c; 834 type d; 835 type bravo; 836 } 837 } 838 839 leaf test-leaf { 840 type union { 841 type uint8; 842 type enumeration { 843 enum zero; 844 enum one; 845 } 846 type enumeration { 847 enum two { 848 value 2; 849 } 850 enum three; 851 } 852 type a; 853 type b; 854 type alpha; 855 } 856 } 857 } // end module`, 858 wantType: &testEnumTypeStruct{ 859 Name: "union", 860 Type: []*testEnumTypeStruct{{ 861 Name: "uint8", 862 }, { 863 Name: "enumeration", 864 ToInt: map[string]int64{"zero": 0, "one": 1}, 865 }, { 866 Name: "enumeration", 867 ToInt: map[string]int64{"two": 2, "three": 3}, 868 }, { 869 Name: "a", 870 ToInt: map[string]int64{"un": 1, "deux": 2}, 871 }, { 872 Name: "b", 873 ToInt: map[string]int64{"trois": 3, "quatre": 4}, 874 }, { 875 Name: "alpha", 876 Type: []*testEnumTypeStruct{{ 877 Name: "uint16", 878 }, { 879 Name: "enumeration", 880 ToInt: map[string]int64{"four": 4, "five": 5}, 881 }, { 882 Name: "enumeration", 883 ToInt: map[string]int64{"six": 6, "seven": 7}, 884 }, { 885 Name: "c", 886 ToInt: map[string]int64{"cinq": 5, "sept": 7}, 887 }, { 888 Name: "d", 889 ToInt: map[string]int64{"huit": 8, "neuf": 9}, 890 }, { 891 Name: "bravo", 892 Type: []*testEnumTypeStruct{{ 893 Name: "uint32", 894 }, { 895 Name: "enumeration", 896 ToInt: map[string]int64{"eight": 8, "nine": 9}, 897 }, { 898 Name: "enumeration", 899 ToInt: map[string]int64{"ten": 10, "eleven": 11}, 900 }, { 901 Name: "e", 902 ToInt: map[string]int64{"dix": 10, "onze": 11}, 903 }, { 904 Name: "f", 905 ToInt: map[string]int64{"douze": 12, "treize": 13}, 906 }}, 907 }}, 908 }}, 909 }, 910 }} 911 912 getTestLeaf := func(ms *Modules) (*YangType, error) { 913 const module = "test" 914 m, ok := ms.Modules[module] 915 if !ok { 916 return nil, fmt.Errorf("can't find module %q", module) 917 } 918 919 if len(m.Leaf) == 0 { 920 return nil, fmt.Errorf("node %v is missing imports", m) 921 } 922 e := ToEntry(m) 923 return e.Dir["test-leaf"].Type, nil 924 } 925 926 for _, tt := range tests { 927 inModules := map[string]string{ 928 "test": ` 929 module test { 930 prefix "t"; 931 namespace "urn:t"; 932 933 ` + tt.leafNode, 934 } 935 936 t.Run(tt.desc, func(t *testing.T) { 937 ms := NewModules() 938 for n, m := range inModules { 939 if err := ms.Parse(m, n); err != nil { 940 t.Fatalf("error parsing module %s, got: %v, want: nil", n, err) 941 } 942 } 943 errs := ms.Process() 944 var err error 945 if len(errs) > 1 { 946 t.Fatalf("Got more than 1 error: %v", errs) 947 } else if len(errs) == 1 { 948 err = errs[0] 949 } 950 if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { 951 t.Errorf("Did not get expected error: %s", diff) 952 } 953 if err != nil { 954 return 955 } 956 957 gotType, err := getTestLeaf(ms) 958 if err != nil { 959 t.Fatal(err) 960 } 961 962 if diff := cmp.Diff(filterTypeNames(gotType), tt.wantType); diff != "" { 963 t.Errorf("Type.resolve() union types test (-got, +want):\n%s", diff) 964 } 965 }) 966 } 967 } 968 969 type testEnumTypeStruct struct { 970 Name string 971 // ToInt is the ToInt map representing the enum value (if present). 972 ToInt map[string]int64 973 Type []*testEnumTypeStruct 974 } 975 976 // filterTypeNames returns a testEnumTypeStruct with only the 977 // YangType.Name fields of the given type, preserving 978 // the recursive structure of the type, to work around cmp not 979 // having an allowlist way of specifying which fields to 980 // compare and YangType having a custom Equal function. 981 func filterTypeNames(ytype *YangType) *testEnumTypeStruct { 982 filteredNames := &testEnumTypeStruct{Name: ytype.Name} 983 if ytype.Enum != nil { 984 filteredNames.ToInt = ytype.Enum.ToInt 985 } 986 for _, subtype := range ytype.Type { 987 filteredNames.Type = append(filteredNames.Type, filterTypeNames(subtype)) 988 } 989 return filteredNames 990 } 991 992 func TestPattern(t *testing.T) { 993 tests := []struct { 994 desc string 995 leafNode string 996 wantType *YangType 997 wantErrSubstr string 998 }{{ 999 desc: "Only normal patterns", 1000 leafNode: ` 1001 leaf test-leaf { 1002 type string { 1003 o:bar 'coo'; 1004 o:bar 'foo'; 1005 pattern 'charlie'; 1006 o:bar 'goo'; 1007 } 1008 } 1009 } // end module`, 1010 wantType: &YangType{ 1011 Pattern: []string{"charlie"}, 1012 }, 1013 }, { 1014 desc: "Only posix patterns", 1015 leafNode: ` 1016 leaf test-leaf { 1017 type string { 1018 o:bar 'coo'; 1019 o:posix-pattern 'bravo'; 1020 o:bar 'foo'; 1021 o:posix-pattern 'charlie'; 1022 o:bar 'goo'; 1023 } 1024 } 1025 } // end module`, 1026 wantType: &YangType{ 1027 POSIXPattern: []string{"bravo", "charlie"}, 1028 }, 1029 }, { 1030 desc: "No patterns", 1031 leafNode: ` 1032 leaf test-leaf { 1033 type string; 1034 } 1035 }`, 1036 wantType: &YangType{ 1037 Pattern: nil, 1038 POSIXPattern: nil, 1039 }, 1040 }, { 1041 desc: "Both patterns", 1042 leafNode: ` 1043 leaf test-leaf { 1044 type string { 1045 pattern 'alpha'; 1046 o:posix-pattern 'bravo'; 1047 o:posix-pattern 'charlie'; 1048 o:bar 'coo'; 1049 o:posix-pattern 'delta'; 1050 } 1051 } 1052 } // end module`, 1053 wantType: &YangType{ 1054 Pattern: []string{"alpha"}, 1055 POSIXPattern: []string{"bravo", "charlie", "delta"}, 1056 }, 1057 }, { 1058 desc: "Both patterns, but with non-openconfig-extensions pretenders", 1059 leafNode: ` 1060 leaf test-leaf { 1061 type string { 1062 pattern 'alpha'; 1063 o:bar 'coo'; 1064 o:posix-pattern 'delta'; 1065 1066 n:posix-pattern 'golf'; 1067 1068 pattern 'bravo'; 1069 o:bar 'foo'; 1070 o:posix-pattern 'echo'; 1071 1072 pattern 'charlie'; 1073 o:bar 'goo'; 1074 o:posix-pattern 'foxtrot'; 1075 1076 n:posix-pattern 'hotel'; 1077 } 1078 } 1079 } // end module`, 1080 wantType: &YangType{ 1081 Pattern: []string{"alpha", "bravo", "charlie"}, 1082 POSIXPattern: []string{"delta", "echo", "foxtrot"}, 1083 }, 1084 }, { 1085 desc: "Union type", 1086 leafNode: ` 1087 leaf test-leaf { 1088 type union { 1089 type string { 1090 pattern 'alpha'; 1091 o:bar 'coo'; 1092 o:posix-pattern 'delta'; 1093 1094 pattern 'bravo'; 1095 o:bar 'foo'; 1096 o:posix-pattern 'echo'; 1097 n:posix-pattern 'echo2'; 1098 1099 pattern 'charlie'; 1100 o:bar 'goo'; 1101 o:posix-pattern 'foxtrot'; 1102 } 1103 type uint64; 1104 } 1105 } 1106 } // end module`, 1107 wantType: &YangType{ 1108 Type: []*YangType{{ 1109 Pattern: []string{"alpha", "bravo", "charlie"}, 1110 POSIXPattern: []string{"delta", "echo", "foxtrot"}, 1111 }, { 1112 Pattern: nil, 1113 POSIXPattern: nil, 1114 }}, 1115 }, 1116 }, { 1117 desc: "Union type -- de-duping string types", 1118 leafNode: ` 1119 leaf test-leaf { 1120 type union { 1121 type string { 1122 pattern 'alpha'; 1123 o:posix-pattern 'alpha'; 1124 } 1125 type string { 1126 pattern 'alpha'; 1127 o:posix-pattern 'alpha'; 1128 } 1129 } 1130 } 1131 } // end module`, 1132 wantType: &YangType{ 1133 Type: []*YangType{{ 1134 Pattern: []string{"alpha"}, 1135 POSIXPattern: []string{"alpha"}, 1136 }}, 1137 }, 1138 }, { 1139 desc: "Union type -- different string types due to different patterns", 1140 leafNode: ` 1141 leaf test-leaf { 1142 type union { 1143 type string { 1144 pattern 'alpha'; 1145 } 1146 type string { 1147 pattern 'bravo'; 1148 } 1149 } 1150 } 1151 } // end module`, 1152 wantType: &YangType{ 1153 Type: []*YangType{{ 1154 Pattern: []string{"alpha"}, 1155 }, { 1156 Pattern: []string{"bravo"}, 1157 }}, 1158 }, 1159 }, { 1160 desc: "Union type -- different string types due to different posix-patterns", 1161 leafNode: ` 1162 leaf test-leaf { 1163 type union { 1164 type string { 1165 o:posix-pattern 'alpha'; 1166 } 1167 type string { 1168 o:posix-pattern 'bravo'; 1169 } 1170 } 1171 } 1172 } // end module`, 1173 wantType: &YangType{ 1174 Type: []*YangType{{ 1175 POSIXPattern: []string{"alpha"}, 1176 }, { 1177 POSIXPattern: []string{"bravo"}, 1178 }}, 1179 }, 1180 }, { 1181 desc: "typedef", 1182 leafNode: ` 1183 leaf test-leaf { 1184 type leaf-type; 1185 } 1186 1187 typedef leaf-type { 1188 type string { 1189 pattern 'alpha'; 1190 o:bar 'coo'; 1191 o:posix-pattern 'delta'; 1192 1193 pattern 'bravo'; 1194 o:bar 'foo'; 1195 o:posix-pattern 'echo'; 1196 1197 pattern 'charlie'; 1198 o:bar 'goo'; 1199 o:posix-pattern 'foxtrot'; 1200 } 1201 } 1202 } // end module`, 1203 wantType: &YangType{ 1204 Pattern: []string{"alpha", "bravo", "charlie"}, 1205 POSIXPattern: []string{"delta", "echo", "foxtrot"}, 1206 }, 1207 }, { 1208 desc: "invalid POSIX pattern", 1209 leafNode: ` 1210 leaf test-leaf { 1211 type leaf-type; 1212 } 1213 1214 typedef leaf-type { 1215 type string { 1216 o:posix-pattern '?'; 1217 } 1218 } 1219 } // end module`, 1220 wantErrSubstr: "bad pattern", 1221 }} 1222 1223 getTestLeaf := func(ms *Modules) (*YangType, error) { 1224 const module = "test" 1225 m, ok := ms.Modules[module] 1226 if !ok { 1227 return nil, fmt.Errorf("can't find module %q", module) 1228 } 1229 1230 if len(m.Leaf) == 0 { 1231 return nil, fmt.Errorf("node %v is missing imports", m) 1232 } 1233 e := ToEntry(m) 1234 return e.Dir["test-leaf"].Type, nil 1235 } 1236 1237 for _, tt := range tests { 1238 inModules := map[string]string{ 1239 "test": ` 1240 module test { 1241 prefix "t"; 1242 namespace "urn:t"; 1243 1244 import non-openconfig-extensions { 1245 prefix "n"; 1246 description "non-openconfig-extensions module"; 1247 } 1248 import openconfig-extensions { 1249 prefix "o"; 1250 description "openconfig-extensions module"; 1251 }` + tt.leafNode, 1252 "openconfig-extensions": ` 1253 module openconfig-extensions { 1254 prefix "o"; 1255 namespace "urn:o"; 1256 1257 extension bar { 1258 argument "baz"; 1259 } 1260 1261 extension posix-pattern { 1262 argument "pattern"; 1263 } 1264 } 1265 `, 1266 "non-openconfig-extensions": ` 1267 module non-openconfig-extensions { 1268 prefix "n"; 1269 namespace "urn:n"; 1270 1271 extension bar { 1272 argument "baz"; 1273 } 1274 1275 extension posix-pattern { 1276 argument "pattern"; 1277 } 1278 } 1279 `, 1280 } 1281 1282 t.Run(tt.desc, func(t *testing.T) { 1283 ms := NewModules() 1284 for n, m := range inModules { 1285 if err := ms.Parse(m, n); err != nil { 1286 t.Fatalf("error parsing module %s, got: %v, want: nil", n, err) 1287 } 1288 } 1289 errs := ms.Process() 1290 var err error 1291 if len(errs) > 1 { 1292 t.Fatalf("Got more than 1 error: %v", errs) 1293 } else if len(errs) == 1 { 1294 err = errs[0] 1295 } 1296 if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { 1297 t.Errorf("Did not get expected error: %s", diff) 1298 } 1299 if err != nil { 1300 return 1301 } 1302 1303 yangType, err := getTestLeaf(ms) 1304 if err != nil { 1305 t.Fatal(err) 1306 } 1307 1308 gotType := &YangType{} 1309 populatePatterns(yangType, gotType) 1310 if diff := cmp.Diff(gotType, tt.wantType, cmpopts.EquateEmpty()); diff != "" { 1311 t.Errorf("Type.resolve() pattern test (-got, +want):\n%s", diff) 1312 } 1313 }) 1314 } 1315 } 1316 1317 // populatePatterns populates targetType with only the 1318 // Pattern/POSIXPattern fields of the given type, preserving 1319 // the recursive structure of the type, to work around cmp not 1320 // having an allowlist way of specifying which fields to 1321 // compare. 1322 func populatePatterns(ytype *YangType, targetType *YangType) { 1323 targetType.Pattern = ytype.Pattern 1324 targetType.POSIXPattern = ytype.POSIXPattern 1325 for _, subtype := range ytype.Type { 1326 targetSubtype := &YangType{} 1327 targetType.Type = append(targetType.Type, targetSubtype) 1328 populatePatterns(subtype, targetSubtype) 1329 } 1330 } 1331 1332 func TestTypeLengthRange(t *testing.T) { 1333 tests := []struct { 1334 desc string 1335 leafNode string 1336 wantType *testRangeTypeStruct 1337 wantErrSubstr string 1338 }{{ 1339 desc: "simple uint32", 1340 leafNode: ` 1341 typedef alpha { 1342 type uint32 { 1343 range "1..4 | 10..20"; 1344 } 1345 } 1346 leaf test-leaf { 1347 type alpha; 1348 } 1349 } // end module`, 1350 wantType: &testRangeTypeStruct{ 1351 Name: "alpha", 1352 Range: YangRange{R(1, 4), R(10, 20)}, 1353 }, 1354 }, { 1355 desc: "inherited uint32", 1356 leafNode: ` 1357 typedef alpha { 1358 type uint32 { 1359 range "1..4 | 10..20"; 1360 } 1361 } 1362 typedef bravo { 1363 type alpha { 1364 range "min..3 | 12..max"; 1365 } 1366 } 1367 leaf test-leaf { 1368 type bravo; 1369 } 1370 } // end module`, 1371 wantType: &testRangeTypeStruct{ 1372 Name: "bravo", 1373 Range: YangRange{R(1, 3), R(12, 20)}, 1374 }, 1375 }, { 1376 desc: "inherited uint32 range violation", 1377 leafNode: ` 1378 typedef alpha { 1379 type uint32 { 1380 range "1..4 | 10..20"; 1381 } 1382 } 1383 typedef bravo { 1384 type alpha { 1385 range "min..max"; 1386 } 1387 } 1388 leaf test-leaf { 1389 type bravo; 1390 } 1391 } // end module`, 1392 wantErrSubstr: "not within", 1393 }, { 1394 desc: "unrestricted decimal64", 1395 leafNode: ` 1396 typedef alpha { 1397 type decimal64 { 1398 fraction-digits 2; 1399 } 1400 } 1401 leaf test-leaf { 1402 type alpha; 1403 } 1404 } // end module`, 1405 wantType: &testRangeTypeStruct{ 1406 Name: "alpha", 1407 Range: YangRange{Rf(MinInt64, MaxInt64, 2)}, 1408 }, 1409 }, { 1410 desc: "simple restricted decimal64", 1411 leafNode: ` 1412 typedef alpha { 1413 type decimal64 { 1414 fraction-digits 2; 1415 range "1 .. 3.14 | 10 | 20..max"; 1416 } 1417 } 1418 leaf test-leaf { 1419 type alpha; 1420 } 1421 } // end module`, 1422 wantType: &testRangeTypeStruct{ 1423 Name: "alpha", 1424 Range: YangRange{Rf(100, 314, 2), Rf(1000, 1000, 2), Rf(2000, MaxInt64, 2)}, 1425 }, 1426 }, { 1427 desc: "simple decimal64 with inherited ranges", 1428 leafNode: ` 1429 typedef alpha { 1430 type decimal64 { 1431 fraction-digits 3; 1432 range "1 .. 3.14 | 10 | 20..max"; 1433 } 1434 } 1435 typedef bravo { 1436 type alpha { 1437 range "min .. 2.72 | 42 .. max"; 1438 } 1439 } 1440 leaf test-leaf { 1441 type bravo; 1442 } 1443 } // end module`, 1444 wantType: &testRangeTypeStruct{ 1445 Name: "bravo", 1446 Range: YangRange{Rf(1000, 2720, 3), Rf(42000, MaxInt64, 3)}, 1447 }, 1448 }, { 1449 desc: "triple-inherited decimal64", 1450 leafNode: ` 1451 typedef alpha { 1452 type decimal64 { 1453 fraction-digits 2; 1454 } 1455 } 1456 typedef bravo { 1457 type alpha { 1458 range "1 .. 3.14 | 10 | 20..max"; 1459 } 1460 } 1461 typedef charlie { 1462 type bravo { 1463 range "min .. 2.72 | 42 .. max"; 1464 } 1465 } 1466 leaf test-leaf { 1467 type charlie; 1468 } 1469 } // end module`, 1470 wantType: &testRangeTypeStruct{ 1471 Name: "charlie", 1472 Range: YangRange{Rf(100, 272, 2), Rf(4200, MaxInt64, 2)}, 1473 }, 1474 }, { 1475 desc: "simple decimal64 with inherited ranges", 1476 leafNode: ` 1477 typedef alpha { 1478 type decimal64 { 1479 fraction-digits 2; 1480 range "1 .. 3.14 | 10 | 20..max"; 1481 } 1482 } 1483 typedef bravo { 1484 type alpha { 1485 range "min..max"; 1486 } 1487 } 1488 leaf test-leaf { 1489 type alpha; 1490 } 1491 } // end module`, 1492 wantErrSubstr: "not within", 1493 }, { 1494 desc: "simple decimal64 with too few fractional digits", 1495 leafNode: ` 1496 typedef alpha { 1497 type decimal64 { 1498 fraction-digits 1; 1499 range "1 .. 3.14 | 10 | 20..max"; 1500 } 1501 } 1502 leaf test-leaf { 1503 type alpha; 1504 } 1505 } // end module`, 1506 wantErrSubstr: "has too much precision", 1507 }, { 1508 desc: "simple decimal64 fractional digit on inherited decimal64 type", 1509 leafNode: ` 1510 typedef alpha { 1511 type decimal64 { 1512 fraction-digits 2; 1513 range "1 .. 3.14 | 10 | 20..max"; 1514 } 1515 } 1516 typedef bravo { 1517 type alpha { 1518 fraction-digits 2; 1519 range "25..max"; 1520 } 1521 } 1522 leaf test-leaf { 1523 type bravo; 1524 } 1525 } // end module`, 1526 wantErrSubstr: "overriding of fraction-digits not allowed", 1527 }, { 1528 desc: "simple string with length", 1529 leafNode: ` 1530 typedef alpha { 1531 type string { 1532 length "1..4 | 10..20 | 30..max"; 1533 } 1534 } 1535 leaf test-leaf { 1536 type alpha; 1537 } 1538 } // end module`, 1539 wantType: &testRangeTypeStruct{ 1540 Name: "alpha", 1541 Length: YangRange{R(1, 4), R(10, 20), YRange{FromInt(30), FromUint(maxUint64)}}, 1542 }, 1543 }, { 1544 desc: "inherited string", 1545 leafNode: ` 1546 typedef alpha { 1547 type string { 1548 length "1..4 | 10..20 | 30..max"; 1549 } 1550 } 1551 typedef bravo { 1552 type alpha { 1553 length "min..3 | 42..max"; 1554 } 1555 } 1556 leaf test-leaf { 1557 type bravo; 1558 } 1559 } // end module`, 1560 wantType: &testRangeTypeStruct{ 1561 Name: "bravo", 1562 Length: YangRange{R(1, 3), YRange{FromInt(42), FromUint(maxUint64)}}, 1563 }, 1564 }, { 1565 desc: "inherited binary", 1566 leafNode: ` 1567 typedef alpha { 1568 type binary { 1569 length "1..4 | 10..20 | 30..max"; 1570 } 1571 } 1572 typedef bravo { 1573 type alpha { 1574 length "min..3 | 42..max"; 1575 } 1576 } 1577 leaf test-leaf { 1578 type bravo; 1579 } 1580 } // end module`, 1581 wantType: &testRangeTypeStruct{ 1582 Name: "bravo", 1583 Length: YangRange{R(1, 3), YRange{FromInt(42), FromUint(maxUint64)}}, 1584 }, 1585 }, { 1586 desc: "inherited string length violation", 1587 leafNode: ` 1588 typedef alpha { 1589 type string { 1590 length "1..4 | 10..20 | 30..max"; 1591 } 1592 } 1593 typedef bravo { 1594 type alpha { 1595 length "min..max"; 1596 } 1597 } 1598 leaf test-leaf { 1599 type bravo; 1600 } 1601 } // end module`, 1602 wantErrSubstr: "not within", 1603 }, { 1604 desc: "simple union", 1605 leafNode: ` 1606 typedef alpha { 1607 type union { 1608 type string; 1609 type binary { 1610 length "min..5|999..max"; 1611 } 1612 type int8 { 1613 range "min..-42|42..max"; 1614 } 1615 type enumeration { 1616 enum zero; 1617 enum one; 1618 enum seven { 1619 value 7; 1620 } 1621 } 1622 } 1623 } 1624 leaf test-leaf { 1625 type alpha; 1626 } 1627 } // end module`, 1628 wantType: &testRangeTypeStruct{ 1629 Name: "alpha", 1630 Type: []*testRangeTypeStruct{{ 1631 Name: "string", 1632 }, { 1633 Name: "binary", 1634 Length: YangRange{R(0, 5), YRange{FromInt(999), FromUint(maxUint64)}}, 1635 }, { 1636 Name: "int8", 1637 Range: YangRange{R(minInt8, -42), R(42, maxInt8)}, 1638 }, { 1639 Name: "enumeration", 1640 }}, 1641 }, 1642 }} 1643 1644 getTestLeaf := func(ms *Modules) (*YangType, error) { 1645 const moduleName = "test" 1646 m, ok := ms.Modules[moduleName] 1647 if !ok { 1648 return nil, fmt.Errorf("module not found: %q", moduleName) 1649 } 1650 if len(m.Leaf) == 0 { 1651 return nil, fmt.Errorf("node %v is missing imports", m) 1652 } 1653 e := ToEntry(m) 1654 return e.Dir["test-leaf"].Type, nil 1655 } 1656 1657 for _, tt := range tests { 1658 inModules := map[string]string{ 1659 "test": ` 1660 module test { 1661 prefix "t"; 1662 namespace "urn:t"; 1663 ` + tt.leafNode, 1664 } 1665 1666 t.Run(tt.desc, func(t *testing.T) { 1667 ms := NewModules() 1668 for n, m := range inModules { 1669 if err := ms.Parse(m, n); err != nil { 1670 t.Fatalf("error parsing module %s, got: %v, want: nil", n, err) 1671 } 1672 } 1673 errs := ms.Process() 1674 var err error 1675 if len(errs) > 1 { 1676 t.Fatalf("Got more than 1 error: %v", errs) 1677 } else if len(errs) == 1 { 1678 err = errs[0] 1679 } 1680 if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { 1681 t.Errorf("Did not get expected error: %s", diff) 1682 } 1683 if err != nil { 1684 return 1685 } 1686 1687 gotType, err := getTestLeaf(ms) 1688 if err != nil { 1689 t.Fatal(err) 1690 } 1691 1692 if diff := cmp.Diff(filterRanges(gotType), tt.wantType); diff != "" { 1693 t.Errorf("Type.resolve() union types test (-got, +want):\n%s", diff) 1694 } 1695 }) 1696 } 1697 } 1698 1699 // testRangeTypeStruct is a filtered-down version of YangType where only certain 1700 // fields are preserved for targeted testing. 1701 type testRangeTypeStruct struct { 1702 Name string 1703 Length YangRange 1704 Range YangRange 1705 Type []*testRangeTypeStruct 1706 } 1707 1708 // filterRanges returns a testRangeTypeStruct with only the Name, Length, and Range 1709 // fields of the given YangType, preserving the recursive structure of the 1710 // type, to work around cmp not having an allowlist way of specifying which 1711 // fields to compare and YangType having a custom Equal function. 1712 func filterRanges(ytype *YangType) *testRangeTypeStruct { 1713 filteredType := &testRangeTypeStruct{Name: ytype.Name} 1714 filteredType.Length = ytype.Length 1715 filteredType.Range = ytype.Range 1716 for _, subtype := range ytype.Type { 1717 filteredType.Type = append(filteredType.Type, filterRanges(subtype)) 1718 } 1719 return filteredType 1720 }