github.com/openconfig/goyang@v1.4.5/pkg/yang/identity_test.go (about) 1 // Copyright 2016 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 "testing" 19 20 "github.com/google/go-cmp/cmp" 21 "github.com/openconfig/gnmi/errdiff" 22 ) 23 24 // inputModule is a mock input YANG module. 25 type inputModule struct { 26 name string // The filename of the YANG module. 27 content string // The contents of the YANG module. 28 } 29 30 type idrefOut struct { 31 module string // The module that the identityref is within. 32 name string // The name of the identityref. 33 values []string // Names of the identities that the identityref relates to. 34 } 35 36 // identityOut is the output for a particular identity within the test case. 37 type identityOut struct { 38 module string // The module that the identity is within. 39 name string // The name of the identity. 40 baseNames []string // The base(s) of the identity as string(s). 41 values []string // The string names of derived identities. 42 } 43 44 // identityTestCase is a test case for a module which contains identities. 45 type identityTestCase struct { 46 name string 47 in []inputModule // The set of input modules for the test 48 identities []identityOut // Slice of the identity values expected 49 idrefs []idrefOut // Slice of identityref results expected 50 wantErrSubstr string // wanErrSubstr is a substring of the wanted error. 51 } 52 53 // getBaseNamesFrom is a utility function for getting the base name(s) of an identity 54 func getBaseNamesFrom(i *Identity) []string { 55 baseNames := []string{} 56 for _, base := range i.Base { 57 baseNames = append(baseNames, base.Name) 58 } 59 return baseNames 60 } 61 62 // Test cases for basic identity extraction. 63 var basicTestCases = []identityTestCase{ 64 { 65 name: "basic-test-case-1: Check identity is found in module.", 66 in: []inputModule{ 67 { 68 name: "idtest-one", 69 content: ` 70 module idtest-one { 71 namespace "urn:idone"; 72 prefix "idone"; 73 74 identity TEST_ID; 75 } 76 `}, 77 }, 78 identities: []identityOut{ 79 {module: "idtest-one", name: "TEST_ID"}, 80 }, 81 }, 82 { 83 name: "basic-test-case-2: Check identity with base is found in module.", 84 in: []inputModule{ 85 { 86 name: "idtest-two", 87 content: ` 88 module idtest-two { 89 namespace "urn:idtwo"; 90 prefix "idone"; 91 92 identity TEST_ID; 93 identity TEST_ID_TWO; 94 identity TEST_CHILD { 95 base TEST_ID; 96 } 97 } 98 `}, 99 }, 100 identities: []identityOut{ 101 {module: "idtest-two", name: "TEST_ID"}, 102 {module: "idtest-two", name: "TEST_ID_TWO"}, 103 {module: "idtest-two", name: "TEST_CHILD", baseNames: []string{"TEST_ID"}}, 104 }, 105 }, 106 { 107 name: "basic-test-case-3: Check identity with multiple bases.", 108 in: []inputModule{ 109 { 110 name: "idtest-three", 111 content: ` 112 module idtest-three { 113 namespace "urn:idthree"; 114 prefix "idthree"; 115 116 identity BASE_ONE; 117 identity BASE_TWO; 118 identity TEST_CHILD_WITH_MULTIPLE_BASES { 119 base BASE_ONE; 120 base BASE_TWO; 121 } 122 } 123 `}, 124 }, 125 identities: []identityOut{ 126 {module: "idtest-three", name: "BASE_ONE"}, 127 {module: "idtest-three", name: "BASE_TWO"}, 128 {module: "idtest-three", name: "TEST_CHILD_WITH_MULTIPLE_BASES", baseNames: []string{"BASE_ONE", "BASE_TWO"}}, 129 }, 130 }, 131 { 132 name: "basic-test-case-4: Check identity base is found from submodule.", 133 in: []inputModule{ 134 { 135 name: "idtest-one", 136 content: ` 137 module idtest-one { 138 namespace "urn:idone"; 139 prefix "idone"; 140 141 include "idtest-one-sub"; 142 143 identity TEST_ID_DERIVED { 144 base TEST_ID; 145 } 146 } 147 `}, 148 { 149 name: "idtest-one-sub", 150 content: ` 151 submodule idtest-one-sub { 152 belongs-to idtest-one { 153 prefix "idone"; 154 } 155 156 identity TEST_ID; 157 } 158 `}, 159 }, 160 identities: []identityOut{ 161 {module: "idtest-one", name: "TEST_ID"}, 162 {module: "idtest-one", name: "TEST_ID_DERIVED", baseNames: []string{"TEST_ID"}}, 163 }, 164 }, 165 { 166 name: "basic-test-case-5: Check identity base is found from module.", 167 in: []inputModule{ 168 { 169 name: "idtest-one", 170 content: ` 171 module idtest-one { 172 namespace "urn:idone"; 173 prefix "idone"; 174 175 include "idtest-one-sub"; 176 177 identity TEST_ID; 178 } 179 `}, 180 { 181 name: "idtest-one-sub", 182 content: ` 183 submodule idtest-one-sub { 184 belongs-to idtest-one { 185 prefix "idone"; 186 } 187 188 identity TEST_ID_DERIVED { 189 base TEST_ID; 190 } 191 } 192 `}, 193 }, 194 identities: []identityOut{ 195 {module: "idtest-one", name: "TEST_ID_DERIVED", baseNames: []string{"TEST_ID"}}, 196 {module: "idtest-one", name: "TEST_ID"}, 197 }, 198 }, 199 } 200 201 // Test the ability to extract identities from a module with the correct base 202 // statements. 203 func TestIdentityExtract(t *testing.T) { 204 for _, tt := range basicTestCases { 205 ms := NewModules() 206 for _, mod := range tt.in { 207 _ = ms.Parse(mod.content, mod.name) 208 } 209 210 for _, ti := range tt.identities { 211 _, err := ms.GetModule(ti.module) 212 213 if err != nil { 214 t.Errorf("Could not parse module : %s", ti.module) 215 continue 216 } 217 218 foundIdentity := false 219 var thisID *Identity 220 for _, ri := range ms.typeDict.identities.dict { 221 moduleName := module(ri.Module).Name 222 if ri.Identity.Name == ti.name && moduleName == ti.module { 223 foundIdentity = true 224 thisID = ri.Identity 225 break 226 } 227 } 228 229 if !foundIdentity { 230 t.Errorf("Could not find identity %s in module %s, identity dict:\n%+v", ti.name, ti.module, ms.typeDict.identities.dict) 231 continue 232 } 233 234 actualBaseNames := getBaseNamesFrom(thisID) 235 if len(ti.baseNames) > 0 { 236 if diff := cmp.Diff(actualBaseNames, ti.baseNames); diff != "" { 237 t.Errorf("(-got, +want):\n%s", diff) 238 } 239 } else { 240 if thisID.Base != nil { 241 t.Errorf("Identity %s had unexpected base(s) %s", thisID.Name, 242 actualBaseNames) 243 } 244 } 245 } 246 } 247 } 248 249 // Test cases for validating that identities can be resolved correctly. 250 var treeTestCases = []identityTestCase{ 251 { 252 name: "tree-test-case-0: Validate identity resolution across submodules", 253 in: []inputModule{ 254 { 255 name: "base.yang", 256 content: ` 257 module base { 258 namespace "urn:base"; 259 prefix "base"; 260 261 include side; 262 263 identity REMOTE_BASE; 264 } 265 `}, 266 { 267 name: "remote.yang", 268 content: ` 269 submodule side { 270 belongs-to base { 271 prefix "r"; 272 } 273 274 identity LOCAL_REMOTE_BASE { 275 base r:REMOTE_BASE; 276 } 277 } 278 `}, 279 }, 280 identities: []identityOut{ 281 { 282 module: "base", 283 name: "REMOTE_BASE", 284 values: []string{"LOCAL_REMOTE_BASE"}, 285 }, 286 }, 287 }, 288 { 289 name: "tree-test-case-1: Validate identity resolution across modules", 290 in: []inputModule{ 291 { 292 name: "base.yang", 293 content: ` 294 module base { 295 namespace "urn:base"; 296 prefix "base"; 297 298 import remote { prefix "r"; } 299 import remote2 { prefix "r2"; } 300 301 identity LOCAL_REMOTE_BASE { 302 base r:REMOTE_BASE; 303 } 304 305 identity LOCAL_REMOTE_BASE2 { 306 base r2:REMOTE_BASE2; 307 } 308 } 309 `}, 310 { 311 name: "remote.yang", 312 content: ` 313 module remote { 314 namespace "urn:remote"; 315 prefix "r"; 316 317 identity REMOTE_BASE; 318 } 319 `}, 320 { 321 name: "remote2.yang", 322 content: ` 323 module remote2 { 324 namespace "urn:remote2"; 325 prefix "remote"; 326 327 identity REMOTE_BASE2; 328 } 329 `}, 330 }, 331 identities: []identityOut{ 332 { 333 module: "remote", 334 name: "REMOTE_BASE", 335 values: []string{"LOCAL_REMOTE_BASE"}, 336 }, 337 { 338 module: "remote2", 339 name: "REMOTE_BASE2", 340 values: []string{"LOCAL_REMOTE_BASE2"}, 341 }, 342 { 343 module: "base", 344 name: "LOCAL_REMOTE_BASE", 345 baseNames: []string{"r:REMOTE_BASE"}, 346 }, 347 { 348 module: "base", 349 name: "LOCAL_REMOTE_BASE2", 350 baseNames: []string{"r2:REMOTE_BASE2"}, 351 }, 352 }, 353 }, 354 { 355 name: "tree-test-case-2: Multi-level inheritance validation.", 356 in: []inputModule{ 357 { 358 name: "base.yang", 359 content: ` 360 module base { 361 namespace "urn:base"; 362 prefix "base"; 363 364 identity GREATGRANDFATHER; 365 identity GRANDFATHER { 366 base "GREATGRANDFATHER"; 367 } 368 identity FATHER { 369 base "GRANDFATHER"; 370 } 371 identity SON { 372 base "FATHER"; 373 } 374 identity UNCLE { 375 base "GRANDFATHER"; 376 } 377 identity BROTHER { 378 base "FATHER"; 379 } 380 identity GREATUNCLE { 381 base "GREATGRANDFATHER"; 382 } 383 } 384 `}, 385 }, 386 identities: []identityOut{ 387 { 388 module: "base", 389 name: "GREATGRANDFATHER", 390 values: []string{ 391 "BROTHER", // Order is alphabetical 392 "FATHER", 393 "GRANDFATHER", 394 "GREATUNCLE", 395 "SON", 396 "UNCLE", 397 }, 398 }, 399 { 400 module: "base", 401 name: "GRANDFATHER", 402 baseNames: []string{"GREATGRANDFATHER"}, 403 values: []string{"BROTHER", "FATHER", "SON", "UNCLE"}, 404 }, 405 { 406 module: "base", 407 name: "GREATUNCLE", 408 baseNames: []string{"GREATGRANDFATHER"}, 409 }, 410 { 411 module: "base", 412 name: "FATHER", 413 baseNames: []string{"GRANDFATHER"}, 414 values: []string{"BROTHER", "SON"}, 415 }, 416 { 417 module: "base", 418 name: "UNCLE", 419 baseNames: []string{"GRANDFATHER"}, 420 }, 421 { 422 module: "base", 423 name: "BROTHER", 424 baseNames: []string{"FATHER"}, 425 }, 426 }, 427 }, 428 { 429 name: "tree-test-case-3", 430 in: []inputModule{ 431 { 432 name: "base.yang", 433 content: ` 434 module base { 435 namespace "urn:base"; 436 prefix "base"; 437 438 identity BASE; 439 identity NOTBASE { 440 base BASE; 441 } 442 443 leaf idref { 444 type identityref { 445 base "BASE"; 446 } 447 } 448 } 449 `}, 450 }, 451 identities: []identityOut{ 452 { 453 module: "base", 454 name: "BASE", 455 values: []string{"NOTBASE"}, 456 }, 457 { 458 module: "base", 459 name: "NOTBASE", 460 baseNames: []string{"BASE"}, 461 }, 462 }, 463 idrefs: []idrefOut{ 464 { 465 module: "base", 466 name: "idref", 467 values: []string{"NOTBASE"}, 468 }, 469 }, 470 }, 471 { 472 name: "tree-test-case-4", 473 in: []inputModule{ 474 { 475 name: "base.yang", 476 content: ` 477 module base4 { 478 namespace "urn:base"; 479 prefix "base4"; 480 481 identity BASE4; 482 identity CHILD4 { 483 base BASE4; 484 } 485 486 typedef t { 487 type identityref { 488 base BASE4; 489 } 490 } 491 492 leaf tref { 493 type t; 494 } 495 } 496 `}, 497 }, 498 identities: []identityOut{ 499 { 500 module: "base4", 501 name: "BASE4", 502 values: []string{"CHILD4"}, 503 }, 504 { 505 module: "base4", 506 name: "CHILD4", 507 baseNames: []string{"BASE4"}, 508 }, 509 }, 510 idrefs: []idrefOut{ 511 { 512 module: "base4", 513 name: "tref", 514 values: []string{"CHILD4"}, 515 }, 516 }, 517 }, 518 { 519 name: "tree-test-case-5", 520 in: []inputModule{ 521 { 522 name: "base.yang", 523 content: ` 524 module base5 { 525 namespace "urn:base"; 526 prefix "base5"; 527 528 identity BASE5A; 529 identity BASE5B; 530 531 identity FIVE_ONE { 532 base BASE5A; 533 } 534 535 identity FIVE_TWO { 536 base BASE5B; 537 } 538 539 leaf union { 540 type union { 541 type identityref { 542 base BASE5A; 543 } 544 type identityref { 545 base BASE5B; 546 } 547 } 548 } 549 }`}, 550 }, 551 identities: []identityOut{ 552 { 553 module: "base5", 554 name: "BASE5A", 555 values: []string{"FIVE_ONE"}, 556 }, 557 { 558 module: "base5", 559 name: "BASE5B", 560 values: []string{"FIVE_TWO"}, 561 }, 562 }, 563 idrefs: []idrefOut{ 564 { 565 module: "base5", 566 name: "union", 567 values: []string{"FIVE_ONE", "FIVE_TWO"}, 568 }, 569 }, 570 }, 571 { 572 name: "identity's base can't be found", 573 in: []inputModule{ 574 { 575 name: "idtest", 576 content: ` 577 module idtest{ 578 namespace "urn:idtwo"; 579 prefix "idone"; 580 581 identity TEST_ID_TWO; 582 identity TEST_CHILD { 583 base TEST_ID; 584 } 585 } 586 `}, 587 }, 588 identities: []identityOut{ 589 {module: "idtest", name: "TEST_ID2"}, 590 }, 591 wantErrSubstr: "can't resolve the local base", 592 }, 593 { 594 name: "identity's base can't be found in remote", 595 in: []inputModule{ 596 { 597 name: "remote.yang", 598 content: ` 599 module remote { 600 namespace "urn:remote"; 601 prefix "remote"; 602 603 identity REMOTE_BASE_ESCAPE; 604 } 605 `}, 606 { 607 name: "base.yang", 608 content: ` 609 module base { 610 namespace "urn:base"; 611 prefix "base"; 612 613 import remote { prefix "r"; } 614 615 identity LOCAL_REMOTE_BASE { 616 base r:REMOTE_BASE; 617 } 618 } 619 `}, 620 }, 621 identities: []identityOut{ 622 {module: "base", name: "LOCAL_REMOTE_BASE"}, 623 }, 624 wantErrSubstr: "can't resolve remote base", 625 }, 626 { 627 name: "identity's base's module can't be found", 628 in: []inputModule{ 629 { 630 name: "remote.yang", 631 content: ` 632 module remote { 633 namespace "urn:remote"; 634 prefix "remote"; 635 636 identity REMOTE_BASE; 637 } 638 `}, 639 { 640 name: "base.yang", 641 content: ` 642 module base { 643 namespace "urn:base"; 644 prefix "base"; 645 646 import remote { prefix "r"; } 647 648 identity LOCAL_REMOTE_BASE { 649 base roe:REMOTE_BASE; 650 } 651 } 652 `}, 653 }, 654 identities: []identityOut{ 655 {module: "base", name: "LOCAL_REMOTE_BASE"}, 656 }, 657 wantErrSubstr: "can't find external module", 658 }, 659 } 660 661 // TestIdentityTree - check inheritance of identities from local and remote 662 // sources. The Values of an Identity correspond to the values that are 663 // referenced by that identity, which need to be inherited. 664 func TestIdentityTree(t *testing.T) { 665 for _, tt := range treeTestCases { 666 t.Run(tt.name, func(t *testing.T) { 667 ms := NewModules() 668 669 for _, mod := range tt.in { 670 _ = ms.Parse(mod.content, mod.name) 671 } 672 673 errs := ms.Process() 674 675 var err error 676 switch len(errs) { 677 case 1: 678 err = errs[0] 679 if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { 680 t.Fatalf("%s", diff) 681 } 682 return 683 case 0: 684 if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { 685 t.Fatalf("%s", diff) 686 } 687 default: 688 t.Fatalf("got multiple errors: %v", errs) 689 } 690 691 // Walk through the identities that are defined in the test case output 692 // and validate that they exist, and their base and values are as expected. 693 for _, chkID := range tt.identities { 694 m, errs := ms.GetModule(chkID.module) 695 if errs != nil { 696 t.Errorf("Couldn't find expected module: %v", errs) 697 continue 698 } 699 700 var foundID *Identity 701 for _, i := range m.Identities { 702 if i.Name == chkID.name { 703 foundID = i 704 break 705 } 706 } 707 708 if foundID == nil { 709 t.Errorf("Couldn't find identity %s in module %s", chkID.name, 710 chkID.module) 711 continue 712 } 713 714 if len(chkID.baseNames) > 0 { 715 actualBaseNames := getBaseNamesFrom(foundID) 716 if diff := cmp.Diff(actualBaseNames, chkID.baseNames); diff != "" { 717 t.Errorf("(-got, +want):\n%s", diff) 718 } 719 } 720 721 valueMap := make(map[string]bool) 722 723 for i, val := range chkID.values { 724 valueMap[val] = false 725 // Check that IsDefined returns the right result 726 if !foundID.IsDefined(val) { 727 t.Errorf("Couldn't find defined value %s for %s", val, chkID.name) 728 } 729 730 // Check that the values are sorted in a consistent order 731 if foundID.Values[i].Name != val { 732 t.Errorf("Invalid order for value #%d. Expecting %s Got %s", i, foundID.Values[i].Name, val) 733 } 734 // Check that GetValue returns the right Identity 735 idval := foundID.GetValue(val) 736 if idval == nil { 737 t.Errorf("Couldn't GetValue(%s) for %s", val, chkID.name) 738 } 739 } 740 741 // Ensure that IsDefined does not return false positives 742 if foundID.IsDefined("DoesNotExist") { 743 t.Errorf("Non-existent value IsDefined for %s", foundID.Name) 744 } 745 746 if foundID.GetValue("DoesNotExist") != nil { 747 t.Errorf("Non-existent value GetValue not nil for %s", foundID.Name) 748 } 749 750 for _, chkv := range foundID.Values { 751 _, ok := valueMap[chkv.Name] 752 if !ok { 753 t.Errorf("Found unexpected value %s for %s", chkv.Name, chkID.name) 754 continue 755 } 756 valueMap[chkv.Name] = true 757 } 758 759 for k, v := range valueMap { 760 if v == false { 761 t.Errorf("Could not find identity %s for %s", k, chkID.name) 762 } 763 } 764 } 765 766 for _, idr := range tt.idrefs { 767 m, errs := ms.GetModule(idr.module) 768 if errs != nil { 769 t.Errorf("Couldn't find expected module %s: %v", idr.module, errs) 770 continue 771 } 772 773 if _, ok := m.Dir[idr.name]; !ok { 774 t.Errorf("Could not find expected identity, got: nil, want: %v", idr.name) 775 continue 776 } 777 778 identity := m.Dir[idr.name] 779 var vals []*Identity 780 switch len(identity.Type.Type) { 781 case 0: 782 vals = identity.Type.IdentityBase.Values 783 default: 784 for _, b := range identity.Type.Type { 785 if b.IdentityBase != nil { 786 vals = append(vals, b.IdentityBase.Values...) 787 } 788 } 789 } 790 791 var valNames []string 792 for _, v := range vals { 793 valNames = append(valNames, v.Name) 794 } 795 796 if diff := cmp.Diff(idr.values, valNames); diff != "" { 797 t.Errorf("Identity %s did not have expected values, (-got, +want):\n%s", idr.name, diff) 798 } 799 } 800 }) 801 } 802 }