github.com/openconfig/goyang@v1.4.5/pkg/yang/entry_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 "bytes" 19 "errors" 20 "fmt" 21 "io/ioutil" 22 "math" 23 "path/filepath" 24 "reflect" 25 "sort" 26 "strings" 27 "testing" 28 29 "github.com/google/go-cmp/cmp" 30 "github.com/google/go-cmp/cmp/cmpopts" 31 "github.com/openconfig/gnmi/errdiff" 32 ) 33 34 func TestNilEntry(t *testing.T) { 35 e := ToEntry(nil) 36 _, ok := e.Node.(*ErrorNode) 37 if !ok { 38 t.Fatalf("ToEntry(nil) did not return an error node") 39 } 40 errs := e.GetErrors() 41 switch len(errs) { 42 default: 43 t.Errorf("got %d errors, wanted 1", len(errs)) 44 fallthrough 45 case 1: 46 got := errs[0].Error() 47 want := "ToEntry called on nil AST node" 48 if got != want { 49 t.Fatalf("got error %q, want %q", got, want) 50 } 51 case 0: 52 t.Fatalf("GetErrors returned no error") 53 } 54 } 55 56 var badInputs = []struct { 57 name string 58 in string 59 errors []string 60 }{ 61 { 62 name: "bad.yang", 63 in: ` 64 // Base test yang module. 65 // This module is syntactally correct (we can build an AST) but it is has 66 // invalid parameters in many statements. 67 module base { 68 namespace "urn:mod"; 69 prefix "base"; 70 71 container c { 72 // bad config value in a container 73 config bad; 74 } 75 container d { 76 leaf bob { 77 // bad config value 78 config incorrect; 79 type unknown; 80 } 81 // duplicate leaf entry bob 82 leaf bob { type string; } 83 // unknown grouping to uses 84 uses the-beatles; 85 } 86 grouping the-group { 87 leaf one { type string; } 88 // duplicate leaf in unused grouping. 89 leaf one { type int; } 90 } 91 uses the-group; 92 } 93 `, 94 errors: []string{ 95 `bad.yang:9:3: invalid config value: bad`, 96 `bad.yang:13:3: duplicate key from bad.yang:20:5: bob`, 97 `bad.yang:14:5: invalid config value: incorrect`, 98 `bad.yang:17:7: unknown type: base:unknown`, 99 `bad.yang:22:5: unknown group: the-beatles`, 100 `bad.yang:24:3: duplicate key from bad.yang:27:5: one`, 101 }, 102 }, 103 { 104 name: "bad-augment.yang", 105 in: ` 106 module base { 107 namespace "urn:mod"; 108 prefix "base"; 109 // augmentation of unknown element 110 augment erewhon { 111 leaf bob { 112 type string; 113 // bad config value in unused augment 114 config wrong; 115 } 116 } 117 } 118 `, 119 errors: []string{ 120 `bad-augment.yang:6:3: augment erewhon not found`, 121 }, 122 }, 123 { 124 name: "bad-min-max-elements.yang", 125 in: ` 126 module base { 127 namespace "urn:mod"; 128 prefix "base"; 129 list foo { 130 // bad arguments to min-elements and max-elements 131 min-elements bar; 132 max-elements -5; 133 } 134 leaf-list bar { 135 type string; 136 // bad arguments to min-elements and max-elements 137 min-elements unbounded; 138 max-elements 122222222222222222222222222222222222222222222222222222222222; 139 } 140 list baz { 141 // good arguments 142 min-elements 0; 143 max-elements unbounded; 144 } 145 list caz { 146 // bad max element: has to be positive. 147 min-elements 0; 148 max-elements 0; 149 } 150 } 151 `, 152 errors: []string{ 153 `bad-min-max-elements.yang:7:5: invalid min-elements value`, 154 `bad-min-max-elements.yang:8:5: invalid max-elements value`, 155 `bad-min-max-elements.yang:13:5: invalid min-elements value`, 156 `bad-min-max-elements.yang:14:5: invalid max-elements value`, 157 `bad-min-max-elements.yang:24:5: invalid max-elements value`, 158 }, 159 }, 160 } 161 162 func TestBadYang(t *testing.T) { 163 for _, tt := range badInputs { 164 ms := NewModules() 165 if err := ms.Parse(tt.in, tt.name); err != nil { 166 t.Fatalf("unexpected error %s", err) 167 } 168 errs := ms.Process() 169 if len(errs) != len(tt.errors) { 170 t.Errorf("got %d errors, want %d", len(errs), len(tt.errors)) 171 } else { 172 ok := true 173 for x, err := range errs { 174 if !strings.Contains(err.Error(), tt.errors[x]) { 175 ok = false 176 break 177 } 178 } 179 if ok { 180 continue 181 } 182 } 183 184 var b bytes.Buffer 185 fmt.Fprint(&b, "got errors:\n") 186 for _, err := range errs { 187 fmt.Fprintf(&b, "\t%v\n", err) 188 } 189 fmt.Fprint(&b, "want errors:\n") 190 for _, err := range tt.errors { 191 fmt.Fprintf(&b, "\t%s\n", err) 192 } 193 t.Error(b.String()) 194 } 195 } 196 197 var parentTestModules = []struct { 198 name string 199 in string 200 }{ 201 { 202 name: "foo.yang", 203 in: ` 204 module foo { 205 namespace "urn:foo"; 206 prefix "foo"; 207 208 import bar { prefix "temp-bar"; } 209 container foo-c { 210 leaf zzz { type string; } 211 leaf-list foo-list { type string; } 212 uses temp-bar:common; 213 } 214 uses temp-bar:common; 215 } 216 `, 217 }, 218 { 219 name: "bar.yang", 220 in: ` 221 module bar { 222 namespace "urn:bar"; 223 prefix "bar"; 224 225 grouping common { 226 container test1 { leaf str { type string; } } 227 container test2 { leaf str { type string; } } 228 } 229 230 container bar-local { 231 leaf test1 { type string; } 232 } 233 234 } 235 `, 236 }, 237 { 238 name: "baz.yang", 239 in: ` 240 module baz { 241 namespace "urn:baz"; 242 prefix "baz"; 243 244 import foo { prefix "f"; } 245 246 grouping baz-common { 247 leaf baz-common-leaf { type string; } 248 container baz-dir { 249 leaf aardvark { type string; } 250 } 251 } 252 253 augment /f:foo-c { 254 uses baz-common; 255 leaf baz-direct-leaf { type string; } 256 } 257 } 258 `, 259 }, 260 { 261 name: "baz-augment.yang", 262 in: ` 263 submodule baz-augment { 264 belongs-to baz { 265 prefix "baz"; 266 } 267 268 import foo { prefix "f"; } 269 270 augment "/f:foo-c" { 271 leaf baz-submod-leaf { type string; } 272 } 273 } 274 `, 275 }, 276 { 277 name: "qux-augment.yang", 278 in: ` 279 submodule qux-augment { 280 belongs-to qux { 281 prefix "qux"; 282 } 283 284 import foo { prefix "f"; } 285 286 augment "/f:foo-c" { 287 leaf qux-submod-leaf { type string; } 288 } 289 } 290 `, 291 }, 292 } 293 294 func TestUsesParent(t *testing.T) { 295 ms := NewModules() 296 for _, tt := range parentTestModules { 297 _ = ms.Parse(tt.in, tt.name) 298 } 299 300 efoo, _ := ms.GetModule("foo") 301 used := efoo.Dir["foo-c"].Dir["test1"] 302 expected := "/foo/foo-c/test1" 303 if used.Path() != expected { 304 t.Errorf("want %s, got %s", expected, used.Path()) 305 } 306 307 used = efoo.Dir["test1"] 308 expected = "/foo/test1" 309 if used.Path() != expected { 310 t.Errorf("want %s, got %s", expected, used.Path()) 311 } 312 } 313 314 func TestPrefixes(t *testing.T) { 315 ms := NewModules() 316 for _, tt := range parentTestModules { 317 _ = ms.Parse(tt.in, tt.name) 318 } 319 320 efoo, _ := ms.GetModule("foo") 321 if efoo.Prefix.Name != "foo" { 322 t.Errorf(`want prefix "foo", got %q`, efoo.Prefix.Name) 323 } 324 325 used := efoo.Dir["foo-c"].Dir["zzz"] 326 if used.Prefix == nil || used.Prefix.Name != "foo" { 327 t.Errorf(`want prefix named "foo", got %#v`, used.Prefix) 328 } 329 330 used = efoo.Dir["foo-c"].Dir["foo-list"] 331 if used.Prefix == nil || used.Prefix.Name != "foo" { 332 t.Errorf(`want prefix named "foo", got %#v`, used.Prefix) 333 } 334 used = efoo.Dir["foo-c"].Dir["test1"] 335 if used.Prefix.Name != "bar" { 336 t.Errorf(`want prefix "bar", got %q`, used.Prefix.Name) 337 } 338 339 used = efoo.Dir["foo-c"].Dir["test1"].Dir["str"] 340 if used.Prefix == nil || used.Prefix.Name != "bar" { 341 t.Errorf(`want prefix named "bar", got %#v`, used.Prefix) 342 } 343 344 } 345 346 func TestEntryNamespace(t *testing.T) { 347 ms := NewModules() 348 for _, tt := range parentTestModules { 349 if err := ms.Parse(tt.in, tt.name); err != nil { 350 t.Fatalf("could not parse module %s: %v", tt.name, err) 351 } 352 } 353 354 if errs := ms.Process(); len(errs) > 0 { 355 t.Fatalf("could not process modules: %v", errs) 356 } 357 358 foo, _ := ms.GetModule("foo") 359 bar, _ := ms.GetModule("bar") 360 361 for _, tc := range []struct { 362 descr string 363 entry *Entry 364 ns string 365 wantMod string 366 wantModError string 367 }{ 368 { 369 descr: "grouping used in foo always have foo's namespace, even if it was defined in bar", 370 entry: foo.Dir["foo-c"].Dir["test1"], 371 ns: "urn:foo", 372 wantMod: "foo", 373 }, 374 { 375 descr: "grouping defined and used in foo has foo's namespace", 376 entry: foo.Dir["foo-c"].Dir["zzz"], 377 ns: "urn:foo", 378 wantMod: "foo", 379 }, 380 { 381 descr: "grouping defined and used in bar has bar's namespace", 382 entry: bar.Dir["bar-local"].Dir["test1"], 383 ns: "urn:bar", 384 wantMod: "bar", 385 }, 386 { 387 descr: "leaf within a used grouping in baz augmented into foo has baz's namespace", 388 entry: foo.Dir["foo-c"].Dir["baz-common-leaf"], 389 ns: "urn:baz", 390 wantMod: "baz", 391 }, 392 { 393 descr: "leaf directly defined within an augment to foo from baz has baz's namespace", 394 entry: foo.Dir["foo-c"].Dir["baz-direct-leaf"], 395 ns: "urn:baz", 396 wantMod: "baz", 397 }, 398 { 399 descr: "leaf directly defined within an augment to foo from submodule baz-augment of baz has baz's namespace", 400 entry: foo.Dir["foo-c"].Dir["baz-submod-leaf"], 401 ns: "urn:baz", 402 wantMod: "baz", 403 }, 404 { 405 descr: "leaf directly defined within an augment to foo from orphan submodule qux-augment has empty namespace", 406 entry: foo.Dir["foo-c"].Dir["qux-submod-leaf"], 407 ns: "", 408 wantModError: `could not find module "" when retrieving namespace for qux-submod-leaf: "": no such namespace`, 409 }, 410 { 411 descr: "children of a container within an augment to from baz have baz's namespace", 412 entry: foo.Dir["foo-c"].Dir["baz-dir"].Dir["aardvark"], 413 ns: "urn:baz", 414 wantMod: "baz", 415 }, 416 } { 417 nsValue := tc.entry.Namespace() 418 if nsValue == nil { 419 t.Errorf("%s: want namespace %s, got nil", tc.descr, tc.ns) 420 } else if tc.ns != nsValue.Name { 421 t.Errorf("%s: want namespace %s, got %s", tc.descr, tc.ns, nsValue.Name) 422 } 423 424 m, err := tc.entry.InstantiatingModule() 425 if err != nil { 426 if tc.wantModError == "" { 427 t.Errorf("%s: %s.InstantiatingModule(): got unexpected error: %v", tc.descr, tc.entry.Path(), err) 428 } else if got := err.Error(); got != tc.wantModError { 429 t.Errorf("%s: %s.InstantiatingModule(): got error: %q, want: %q", tc.descr, tc.entry.Path(), got, tc.wantModError) 430 } 431 continue 432 } else if tc.wantModError != "" { 433 t.Errorf("%s: %s.InstantiatingModule(): got no error, want: %q", tc.descr, tc.entry.Path(), tc.wantModError) 434 continue 435 } 436 437 if m != tc.wantMod { 438 t.Errorf("%s: %s.InstantiatingModule(): did not get expected name, got: %v, want: %v", 439 tc.descr, tc.entry.Path(), m, tc.wantMod) 440 } 441 } 442 } 443 444 var testWhenModules = []struct { 445 name string 446 in string 447 }{ 448 { 449 name: "when.yang", 450 in: ` 451 module when { 452 namespace "urn:when"; 453 prefix "when"; 454 455 leaf condition { type string; } 456 457 container alpha { 458 when "../condition = 'alpha'"; 459 } 460 461 leaf beta { 462 when "../condition = 'beta'"; 463 type string; 464 } 465 466 leaf-list gamma { 467 when "../condition = 'gamma'"; 468 type string; 469 } 470 471 list delta { 472 when "../condition = 'delta'"; 473 } 474 475 choice epsilon { 476 when "../condition = 'epsilon'"; 477 478 case zeta { 479 when "../condition = 'zeta'"; 480 } 481 } 482 483 anyxml eta { 484 when "../condition = 'eta'"; 485 } 486 487 anydata theta { 488 when "../condition = 'theta'"; 489 } 490 491 uses iota { 492 when "../condition = 'iota'"; 493 } 494 495 grouping iota { 496 } 497 498 augment "../alpha" { 499 when "../condition = 'kappa'"; 500 } 501 } 502 `, 503 }, 504 } 505 506 func TestGetWhenXPath(t *testing.T) { 507 ms := NewModules() 508 ms.ParseOptions.StoreUses = true 509 for _, tt := range testWhenModules { 510 if err := ms.Parse(tt.in, tt.name); err != nil { 511 t.Fatalf("could not parse module %s: %v", tt.name, err) 512 } 513 } 514 515 if errs := ms.Process(); len(errs) > 0 { 516 t.Fatalf("could not process modules: %v", errs) 517 } 518 519 when, _ := ms.GetModule("when") 520 521 testcases := []struct { 522 descr string 523 childName string 524 isCase bool 525 choiceName string 526 isAugment bool 527 augmentTarget string 528 }{ 529 { 530 descr: "extract when statement from *Container", 531 childName: "alpha", 532 }, { 533 descr: "extract when statement from *Leaf", 534 childName: "beta", 535 }, { 536 descr: "extract when statement from *LeafList", 537 childName: "gamma", 538 }, { 539 descr: "extract when statement from *List", 540 childName: "delta", 541 }, { 542 descr: "extract when statement from *Choice", 543 childName: "epsilon", 544 }, { 545 descr: "extract when statement from *Case", 546 childName: "zeta", 547 isCase: true, 548 choiceName: "epsilon", 549 }, { 550 descr: "extract when statement from *AnyXML", 551 childName: "eta", 552 }, { 553 descr: "extract when statement from *AnyData", 554 childName: "theta", 555 }, { 556 descr: "extract when statement from *Augment", 557 childName: "kappa", 558 isAugment: true, 559 augmentTarget: "alpha", 560 }, 561 } 562 563 for _, tc := range testcases { 564 parentEntry := when 565 t.Run(tc.descr, func(t *testing.T) { 566 var child *Entry 567 568 if tc.isAugment { 569 child = parentEntry.Dir[tc.augmentTarget].Augmented[0] 570 } else { 571 if tc.isCase { 572 parentEntry = parentEntry.Dir[tc.choiceName] 573 } 574 child = parentEntry.Dir[tc.childName] 575 } 576 577 expectedWhen := "../condition = '" + tc.childName + "'" 578 579 if gotWhen, ok := child.GetWhenXPath(); !ok { 580 t.Errorf("Cannot get when statement of child entry %v", tc.childName) 581 } else if gotWhen != expectedWhen { 582 t.Errorf("Expected when XPath %v, but got %v", expectedWhen, gotWhen) 583 } 584 }) 585 } 586 } 587 588 var testAugmentAndUsesModules = []struct { 589 name string 590 in string 591 }{ 592 { 593 name: "original.yang", 594 in: ` 595 module original { 596 namespace "urn:original"; 597 prefix "orig"; 598 599 import groupings { 600 prefix grp; 601 } 602 603 container alpha { 604 leaf beta { 605 type string; 606 } 607 leaf psi { 608 type string; 609 } 610 leaf omega { 611 type string; 612 } 613 uses grp:nestedLevel0 { 614 when "beta = 'holaWorld'"; 615 } 616 } 617 } 618 `, 619 }, 620 { 621 name: "augments.yang", 622 in: ` 623 module augments { 624 namespace "urn:augments"; 625 prefix "aug"; 626 627 import original { 628 prefix orig; 629 } 630 631 import groupings { 632 prefix grp; 633 } 634 635 augment "/orig:alpha" { 636 when "orig:beta = 'helloWorld'"; 637 638 container charlie { 639 leaf charlieLeaf { 640 type string; 641 } 642 } 643 } 644 645 grouping delta { 646 container echo { 647 leaf echoLeaf { 648 type string; 649 } 650 } 651 } 652 653 augment "/orig:alpha" { 654 when "orig:omega = 'privetWorld'"; 655 uses delta { 656 when "current()/orig:beta = 'nihaoWorld'"; 657 } 658 } 659 } 660 `, 661 }, 662 { 663 name: "groupings.yang", 664 in: ` 665 module groupings { 666 namespace "urn:groupings"; 667 prefix "grp"; 668 669 import "original" { 670 prefix orig; 671 } 672 673 grouping nestedLevel0 { 674 leaf leafAtLevel0 { 675 type string; 676 } 677 uses nestedLevel1 { 678 when "orig:psi = 'geiasouWorld'"; 679 } 680 } 681 682 grouping nestedLevel1 { 683 leaf leafAtLevel1 { 684 type string; 685 } 686 uses nestedLevel2 { 687 when "orig:omega = 'salveWorld'"; 688 } 689 } 690 691 grouping nestedLevel2 { 692 leaf leafAtLevel2 { 693 type string; 694 } 695 } 696 } 697 `, 698 }, 699 } 700 701 func TestAugmentedEntry(t *testing.T) { 702 ms := NewModules() 703 for _, tt := range testAugmentAndUsesModules { 704 if err := ms.Parse(tt.in, tt.name); err != nil { 705 t.Fatalf("could not parse module %s: %v", tt.name, err) 706 } 707 } 708 709 if errs := ms.Process(); len(errs) > 0 { 710 t.Fatalf("could not process modules: %v", errs) 711 } 712 713 orig, _ := ms.GetModule("original") 714 715 testcases := []struct { 716 descr string 717 augmentEntry *Entry 718 augmentWhenStmt string 719 augmentChildNames map[string]bool 720 }{ 721 { 722 descr: "leaf charlie is augmented to container alpha", 723 augmentEntry: orig.Dir["alpha"].Augmented[0], 724 augmentWhenStmt: "orig:beta = 'helloWorld'", 725 augmentChildNames: map[string]bool{ 726 "charlie": false, 727 }, 728 }, { 729 descr: "grouping delta is augmented to container alpha", 730 augmentEntry: orig.Dir["alpha"].Augmented[1], 731 augmentWhenStmt: "orig:omega = 'privetWorld'", 732 augmentChildNames: map[string]bool{ 733 "echo": false, 734 }, 735 }, 736 } 737 738 for _, tc := range testcases { 739 t.Run(tc.descr, func(t *testing.T) { 740 augment := tc.augmentEntry 741 742 if tc.augmentWhenStmt != "" { 743 if gotAugmentWhenStmt, ok := augment.GetWhenXPath(); !ok { 744 t.Errorf("Expected augment when statement %v, but not present", 745 tc.augmentWhenStmt) 746 } else if gotAugmentWhenStmt != tc.augmentWhenStmt { 747 t.Errorf("Expected augment when statement %v, but got %v", 748 tc.augmentWhenStmt, gotAugmentWhenStmt) 749 } 750 } 751 752 for name, entry := range augment.Dir { 753 if _, ok := tc.augmentChildNames[name]; ok { 754 tc.augmentChildNames[name] = true 755 } else { 756 t.Errorf("Got unexpected child name %v in augment", name) 757 } 758 759 if entry.Dir != nil { 760 t.Errorf("Expected augment's child entry %v have nil dir, but got %v", 761 name, entry.Dir) 762 } 763 } 764 765 for name, matched := range tc.augmentChildNames { 766 if !matched { 767 t.Errorf("Expected child name %v in augment, but not present", name) 768 } 769 } 770 771 }) 772 } 773 } 774 775 func TestUsesEntry(t *testing.T) { 776 ms := NewModules() 777 ms.ParseOptions.StoreUses = true 778 for _, tt := range testAugmentAndUsesModules { 779 if err := ms.Parse(tt.in, tt.name); err != nil { 780 t.Fatalf("could not parse module %s: %v", tt.name, err) 781 } 782 } 783 784 if errs := ms.Process(); len(errs) > 0 { 785 t.Fatalf("could not process modules: %v", errs) 786 } 787 788 orig, _ := ms.GetModule("original") 789 790 testcases := []struct { 791 descr string 792 usesParentEntry *Entry 793 usesWhenStmts []string 794 groupingChildNames []map[string]bool 795 nestedLevel int 796 }{ 797 { 798 descr: "second augment in augments.yang uses grouping delta", 799 usesParentEntry: orig.Dir["alpha"].Augmented[1], 800 usesWhenStmts: []string{"current()/orig:beta = 'nihaoWorld'"}, 801 groupingChildNames: []map[string]bool{{"echo": false}}, 802 }, { 803 descr: "container alpha uses nested grouping nestedLevel0", 804 usesParentEntry: orig.Dir["alpha"], 805 usesWhenStmts: []string{ 806 "beta = 'holaWorld'", 807 "orig:psi = 'geiasouWorld'", 808 "orig:omega = 'salveWorld'", 809 }, 810 groupingChildNames: []map[string]bool{ 811 {"leafAtLevel0": false, "leafAtLevel1": false, "leafAtLevel2": false}, 812 {"leafAtLevel1": false, "leafAtLevel2": false}, 813 {"leafAtLevel2": false}, 814 }, 815 nestedLevel: 2, 816 }, 817 } 818 819 for _, tc := range testcases { 820 t.Run(tc.descr, func(t *testing.T) { 821 usesParentEntry := tc.usesParentEntry 822 for i := 0; i <= tc.nestedLevel; i++ { 823 usesStmts := usesParentEntry.Uses 824 // want the usesStmts to have length 1, otherwise also need to verify 825 // every usesStmt slice element is expected. 826 if len(usesStmts) != 1 { 827 t.Errorf("Expected usesStmts to have length 1, but got %v", 828 len(usesStmts)) 829 } 830 831 usesNode := usesStmts[0].Uses 832 grouping := usesStmts[0].Grouping 833 834 if tc.usesWhenStmts[i] != "" { 835 if gotUsesWhenStmt, ok := usesNode.When.Statement().Arg(); !ok { 836 t.Errorf("Expected uses when statement %v, but not present", 837 tc.usesWhenStmts[i]) 838 } else if gotUsesWhenStmt != tc.usesWhenStmts[i] { 839 t.Errorf("Expected uses when statement %v, but got %v", 840 tc.usesWhenStmts[i], gotUsesWhenStmt) 841 } 842 } 843 844 for name, entry := range grouping.Dir { 845 if _, ok := tc.groupingChildNames[i][name]; ok { 846 tc.groupingChildNames[i][name] = true 847 } else { 848 t.Errorf("Got unexpected child name %v in uses", name) 849 } 850 851 if entry.Dir != nil { 852 t.Errorf("Expected uses's child entry %v have nil dir, but got %v", 853 name, entry.Dir) 854 } 855 } 856 857 for name, matched := range tc.groupingChildNames[i] { 858 if !matched { 859 t.Errorf("Expected child name %v in grouping %v, but not present", 860 name, grouping.Name) 861 } 862 } 863 usesParentEntry = grouping 864 } 865 866 }) 867 } 868 } 869 870 func TestShallowDup(t *testing.T) { 871 testModule := struct { 872 name string 873 in string 874 }{ 875 876 name: "mod.yang", 877 in: ` 878 module mod { 879 namespace "urn:mod"; 880 prefix "mod"; 881 882 container level0 { 883 container level1-1 { 884 leaf level2-1 { type string;} 885 } 886 887 container level1-2 { 888 leaf level2-2 { type string;} 889 } 890 891 container level1-3{ 892 container level2-3 { 893 leaf level3-1 { type string;} 894 } 895 } 896 } 897 } 898 `, 899 } 900 901 ms := NewModules() 902 903 if err := ms.Parse(testModule.in, testModule.name); err != nil { 904 t.Fatalf("could not parse module %s: %v", testModule.name, err) 905 } 906 907 if errs := ms.Process(); len(errs) > 0 { 908 t.Fatalf("could not process modules: %v", errs) 909 } 910 911 mod, _ := ms.GetModule("mod") 912 level0 := mod.Dir["level0"] 913 level0ShallowDup := level0.shallowDup() 914 915 for name, entry := range level0.Dir { 916 shallowDupedEntry, ok := level0ShallowDup.Dir[name] 917 if !ok { 918 t.Errorf("Expect shallowDup() to duplicate direct child %v, but did not", name) 919 } 920 if len(entry.Dir) != 1 { 921 t.Errorf("Expect original entry's direct child have length 1 dir") 922 } 923 if shallowDupedEntry.Dir != nil { 924 t.Errorf("Expect shallowDup()'ed entry's direct child to have nil dir") 925 } 926 } 927 } 928 929 func TestIgnoreCircularDependencies(t *testing.T) { 930 tests := []struct { 931 name string 932 inModules map[string]string 933 inIgnoreCircDep bool 934 wantErrs bool 935 }{{ 936 name: "validation that non-circular dependencies are correct", 937 inModules: map[string]string{ 938 "mod-a": ` 939 module mod-a { 940 namespace "urn:a"; 941 prefix "a"; 942 943 include subm-x; 944 include subm-y; 945 946 leaf marker { type string; } 947 } 948 `, 949 "subm-x": ` 950 submodule subm-x { 951 belongs-to mod-a { prefix a; } 952 } 953 `, 954 "subm-y": ` 955 submodule subm-y { 956 belongs-to mod-a { prefix a; } 957 // Not circular. 958 include subm-x; 959 } 960 `}, 961 }, { 962 name: "circular dependency error identified", 963 inModules: map[string]string{ 964 "mod-a": ` 965 module mod-a { 966 namespace "urn:a"; 967 prefix "a"; 968 969 include subm-x; 970 include subm-y; 971 972 leaf marker { type string; } 973 } 974 `, 975 "subm-x": ` 976 submodule subm-x { 977 belongs-to mod-a { prefix a; } 978 // Circular 979 include subm-y; 980 } 981 `, 982 "subm-y": ` 983 submodule subm-y { 984 belongs-to mod-a { prefix a; } 985 // Circular 986 include subm-x; 987 } 988 `}, 989 wantErrs: true, 990 }, { 991 name: "circular dependency error skipped", 992 inModules: map[string]string{ 993 "mod-a": ` 994 module mod-a { 995 namespace "urn:a"; 996 prefix "a"; 997 998 include subm-x; 999 include subm-y; 1000 1001 leaf marker { type string; } 1002 } 1003 `, 1004 "subm-x": ` 1005 submodule subm-x { 1006 belongs-to mod-a { prefix a; } 1007 // Circular 1008 include subm-y; 1009 } 1010 `, 1011 "subm-y": ` 1012 submodule subm-y { 1013 belongs-to mod-a { prefix a; } 1014 // Circular 1015 include subm-x; 1016 } 1017 `}, 1018 inIgnoreCircDep: true, 1019 }} 1020 1021 for _, tt := range tests { 1022 ms := NewModules() 1023 ms.ParseOptions.IgnoreSubmoduleCircularDependencies = tt.inIgnoreCircDep 1024 for n, m := range tt.inModules { 1025 if err := ms.Parse(m, n); err != nil { 1026 if !tt.wantErrs { 1027 t.Errorf("%s: could not parse modules, got: %v, want: nil", tt.name, err) 1028 } 1029 continue 1030 } 1031 } 1032 } 1033 } 1034 1035 func TestEntryDefaultValue(t *testing.T) { 1036 getdir := func(e *Entry, elements ...string) (*Entry, error) { 1037 for _, elem := range elements { 1038 next := e.Dir[elem] 1039 if next == nil { 1040 return nil, fmt.Errorf("%s missing directory %q", e.Path(), elem) 1041 } 1042 e = next 1043 } 1044 return e, nil 1045 } 1046 1047 modtext := ` 1048 module defaults { 1049 namespace "urn:defaults"; 1050 prefix "defaults"; 1051 1052 typedef string-default { 1053 type string; 1054 default "typedef default value"; 1055 } 1056 1057 typedef string-emptydefault { 1058 type string; 1059 default ""; 1060 } 1061 1062 grouping common { 1063 container common-nodefault { 1064 leaf string { 1065 type string; 1066 } 1067 } 1068 container common-withdefault { 1069 leaf string { 1070 type string; 1071 default "default value"; 1072 } 1073 } 1074 container common-withemptydefault { 1075 leaf string { 1076 type string; 1077 default ""; 1078 } 1079 } 1080 container common-typedef-withdefault { 1081 leaf string { 1082 type string-default; 1083 } 1084 } 1085 container common-typedef-withemptydefault { 1086 leaf string { 1087 type string-emptydefault; 1088 } 1089 } 1090 } 1091 1092 container defaults { 1093 leaf mandatory-default { 1094 type string-default; 1095 mandatory true; 1096 } 1097 leaf uint32-withdefault { 1098 type uint32; 1099 default 13; 1100 } 1101 leaf string-withdefault { 1102 type string-default; 1103 } 1104 leaf nodefault { 1105 type string; 1106 } 1107 uses common; 1108 1109 choice choice-default { 1110 case alpha { 1111 leaf alpha { 1112 type string; 1113 } 1114 } 1115 case zeta { 1116 leaf zeta { 1117 type string; 1118 } 1119 } 1120 default zeta; 1121 } 1122 } 1123 1124 grouping leaflist-common { 1125 container common-nodefault { 1126 leaf string { 1127 type string; 1128 } 1129 } 1130 container common-withdefault { 1131 leaf-list string { 1132 type string; 1133 default "default value"; 1134 } 1135 } 1136 container common-typedef-withdefault { 1137 leaf string { 1138 type string-default; 1139 } 1140 } 1141 } 1142 1143 container leaflist-defaults { 1144 leaf-list uint32-withdefault { 1145 type uint32; 1146 default "13"; 1147 default 14; 1148 } 1149 leaf-list stringlist-withdefault { 1150 type string-default; 1151 } 1152 leaf-list stringlist-withemptydefault { 1153 type string-emptydefault; 1154 } 1155 leaf-list stringlist-withdefault-withminelem { 1156 type string-default; 1157 min-elements 1; 1158 } 1159 leaf-list emptydefault { 1160 type string; 1161 default ""; 1162 } 1163 leaf-list nodefault { 1164 type string; 1165 } 1166 uses leaflist-common; 1167 } 1168 1169 } 1170 ` 1171 1172 ms := NewModules() 1173 if err := ms.Parse(modtext, "defaults.yang"); err != nil { 1174 t.Fatal(err) 1175 } 1176 1177 for i, tc := range []struct { 1178 wantSingle string 1179 wantSingleOk bool 1180 wantDefaults []string 1181 path []string 1182 }{ 1183 { 1184 path: []string{"defaults", "string-withdefault"}, 1185 wantSingle: "typedef default value", 1186 wantDefaults: []string{"typedef default value"}, 1187 wantSingleOk: true, 1188 }, 1189 { 1190 path: []string{"defaults", "uint32-withdefault"}, 1191 wantSingle: "13", 1192 wantDefaults: []string{"13"}, 1193 wantSingleOk: true, 1194 }, 1195 { 1196 path: []string{"defaults", "nodefault"}, 1197 wantSingle: "", 1198 wantDefaults: nil, 1199 }, 1200 { 1201 path: []string{"defaults", "common-withdefault", "string"}, 1202 wantSingle: "default value", 1203 wantDefaults: []string{"default value"}, 1204 wantSingleOk: true, 1205 }, 1206 { 1207 path: []string{"defaults", "common-withemptydefault", "string"}, 1208 wantSingle: "", 1209 wantDefaults: []string{""}, 1210 wantSingleOk: true, 1211 }, 1212 { 1213 path: []string{"defaults", "common-typedef-withdefault", "string"}, 1214 wantSingle: "typedef default value", 1215 wantDefaults: []string{"typedef default value"}, 1216 wantSingleOk: true, 1217 }, 1218 { 1219 path: []string{"defaults", "common-typedef-withemptydefault", "string"}, 1220 wantSingle: "", 1221 wantDefaults: []string{""}, 1222 wantSingleOk: true, 1223 }, 1224 { 1225 path: []string{"defaults", "common-nodefault", "string"}, 1226 wantSingle: "", 1227 wantDefaults: nil, 1228 }, 1229 { 1230 path: []string{"defaults", "mandatory-default"}, 1231 wantSingle: "", 1232 wantDefaults: nil, 1233 }, 1234 { 1235 path: []string{"defaults", "choice-default"}, 1236 wantSingle: "zeta", 1237 wantDefaults: []string{"zeta"}, 1238 wantSingleOk: true, 1239 }, 1240 { 1241 path: []string{"leaflist-defaults", "uint32-withdefault"}, 1242 wantSingle: "", 1243 wantDefaults: []string{"13", "14"}, 1244 }, 1245 { 1246 path: []string{"leaflist-defaults", "stringlist-withdefault"}, 1247 wantSingle: "typedef default value", 1248 wantDefaults: []string{"typedef default value"}, 1249 wantSingleOk: true, 1250 }, 1251 { 1252 path: []string{"leaflist-defaults", "stringlist-withemptydefault"}, 1253 wantSingle: "", 1254 wantDefaults: []string{""}, 1255 wantSingleOk: true, 1256 }, 1257 { 1258 path: []string{"leaflist-defaults", "stringlist-withdefault-withminelem"}, 1259 wantSingle: "", 1260 wantDefaults: nil, 1261 }, 1262 { 1263 path: []string{"leaflist-defaults", "emptydefault"}, 1264 wantSingle: "", 1265 wantDefaults: []string{""}, 1266 wantSingleOk: true, 1267 }, 1268 { 1269 path: []string{"leaflist-defaults", "nodefault"}, 1270 wantSingle: "", 1271 wantDefaults: nil, 1272 }, 1273 { 1274 path: []string{"leaflist-defaults", "common-nodefault", "string"}, 1275 wantSingle: "", 1276 wantDefaults: nil, 1277 }, 1278 { 1279 path: []string{"leaflist-defaults", "common-withdefault", "string"}, 1280 wantSingle: "default value", 1281 wantDefaults: []string{"default value"}, 1282 wantSingleOk: true, 1283 }, 1284 { 1285 path: []string{"leaflist-defaults", "common-typedef-withdefault", "string"}, 1286 wantSingle: "typedef default value", 1287 wantDefaults: []string{"typedef default value"}, 1288 wantSingleOk: true, 1289 }, 1290 } { 1291 tname := strings.Join(tc.path, "/") 1292 1293 mod, ok := ms.Modules["defaults"] 1294 if !ok { 1295 t.Fatalf("[%d] module not found: %q", i, tname) 1296 } 1297 defaults := ToEntry(mod) 1298 dir, err := getdir(defaults, tc.path...) 1299 if err != nil { 1300 t.Fatalf("[%d_%s] could not retrieve path: %v", i, tname, err) 1301 } 1302 if got, gotOk := dir.SingleDefaultValue(); got != tc.wantSingle || gotOk != tc.wantSingleOk { 1303 t.Errorf("[%d_%s] got SingleDefaultValue (%q, %v), want (%q, %v)", i, tname, got, gotOk, tc.wantSingle, tc.wantSingleOk) 1304 } 1305 if diff := cmp.Diff(dir.DefaultValues(), tc.wantDefaults); diff != "" { 1306 t.Errorf("[%d_%s] DefaultValues (-got, +want):\n%s", i, tname, diff) 1307 } 1308 } 1309 } 1310 1311 func TestFullModuleProcess(t *testing.T) { 1312 tests := []struct { 1313 name string 1314 inModules map[string]string 1315 inIgnoreCircDeps bool 1316 wantLeaves map[string][]string 1317 customVerify func(t *testing.T, module *Entry) 1318 wantErr bool 1319 }{{ 1320 name: "circular import via child", 1321 inModules: map[string]string{ 1322 "test": ` 1323 module test { 1324 prefix "t"; 1325 namespace "urn:t"; 1326 1327 include test-router; 1328 include test-router-bgp; 1329 include test-router-isis; 1330 1331 container configure { 1332 uses test-router; 1333 } 1334 }`, 1335 "test-router": ` 1336 submodule test-router { 1337 belongs-to test { prefix "t"; } 1338 1339 include test-router-bgp; 1340 include test-router-isis; 1341 include test-router-ldp; 1342 1343 grouping test-router { 1344 uses test-router-ldp; 1345 } 1346 }`, 1347 "test-router-ldp": ` 1348 submodule test-router-ldp { 1349 belongs-to test { prefix "t"; } 1350 1351 grouping test-router-ldp { } 1352 }`, 1353 "test-router-isis": ` 1354 submodule test-router-isis { 1355 belongs-to test { prefix "t"; } 1356 1357 include test-router; 1358 }`, 1359 "test-router-bgp": ` 1360 submodule test-router-bgp { 1361 belongs-to test { prefix "t"; } 1362 }`, 1363 }, 1364 inIgnoreCircDeps: true, 1365 }, { 1366 name: "non-circular via child", 1367 inModules: map[string]string{ 1368 "bgp": ` 1369 module bgp { 1370 prefix "bgp"; 1371 namespace "urn:bgp"; 1372 1373 include bgp-son; 1374 include bgp-daughter; 1375 1376 leaf parent { type string; } 1377 }`, 1378 "bgp-son": ` 1379 submodule bgp-son { 1380 belongs-to bgp { prefix "bgp"; } 1381 1382 leaf son { type string; } 1383 }`, 1384 "bgp-daughter": ` 1385 submodule bgp-daughter { 1386 belongs-to bgp { prefix "bgp"; } 1387 include bgp-son; 1388 1389 leaf daughter { type string; } 1390 }`, 1391 }, 1392 }, { 1393 name: "simple circular via child", 1394 inModules: map[string]string{ 1395 "parent": ` 1396 module parent { 1397 prefix "p"; 1398 namespace "urn:p"; 1399 include son; 1400 include daughter; 1401 1402 leaf p { type string; } 1403 } 1404 `, 1405 "son": ` 1406 submodule son { 1407 belongs-to parent { prefix "p"; } 1408 include daughter; 1409 1410 leaf s { type string; } 1411 } 1412 `, 1413 "daughter": ` 1414 submodule daughter { 1415 belongs-to parent { prefix "p"; } 1416 include son; 1417 1418 leaf d { type string; } 1419 } 1420 `, 1421 }, 1422 wantErr: true, 1423 }, { 1424 name: "merge via grandchild", 1425 inModules: map[string]string{ 1426 "bgp": ` 1427 module bgp { 1428 prefix "bgp"; 1429 namespace "urn:bgp"; 1430 1431 include bgp-son; 1432 1433 leaf parent { type string; } 1434 }`, 1435 "bgp-son": ` 1436 submodule bgp-son { 1437 belongs-to bgp { prefix "bgp"; } 1438 1439 include bgp-grandson; 1440 1441 leaf son { type string; } 1442 }`, 1443 "bgp-grandson": ` 1444 submodule bgp-grandson { 1445 belongs-to bgp { prefix "bgp"; } 1446 1447 leaf grandson { type string; } 1448 }`, 1449 }, 1450 wantLeaves: map[string][]string{ 1451 "bgp": {"parent", "son", "grandson"}, 1452 }, 1453 }, { 1454 name: "parent to son and daughter with common grandchild", 1455 inModules: map[string]string{ 1456 "parent": ` 1457 module parent { 1458 prefix "p"; 1459 namespace "urn:p"; 1460 include son; 1461 include daughter; 1462 1463 leaf p { type string; } 1464 } 1465 `, 1466 "son": ` 1467 submodule son { 1468 belongs-to parent { prefix "p"; } 1469 include grandchild; 1470 1471 leaf s { type string; } 1472 } 1473 `, 1474 "daughter": ` 1475 submodule daughter { 1476 belongs-to parent { prefix "p"; } 1477 include grandchild; 1478 1479 leaf d { type string; } 1480 } 1481 `, 1482 "grandchild": ` 1483 submodule grandchild { 1484 belongs-to parent { prefix "p"; } 1485 1486 leaf g { type string; } 1487 } 1488 `, 1489 }, 1490 wantLeaves: map[string][]string{ 1491 "parent": {"p", "s", "d", "g"}, 1492 }, 1493 }, { 1494 name: "parent to son and daughter, not a circdep", 1495 inModules: map[string]string{ 1496 "parent": ` 1497 module parent { 1498 prefix "p"; 1499 namespace "urn:p"; 1500 1501 include son; 1502 include daughter; 1503 1504 uses son-group; 1505 } 1506 `, 1507 "son": ` 1508 submodule son { 1509 belongs-to parent { prefix "p"; } 1510 include daughter; 1511 1512 grouping son-group { 1513 uses daughter-group; 1514 } 1515 } 1516 `, 1517 "daughter": ` 1518 submodule daughter { 1519 belongs-to parent { prefix "p"; } 1520 1521 grouping daughter-group { 1522 leaf s { type string; } 1523 } 1524 1525 leaf d { type string; } 1526 } 1527 `, 1528 }, 1529 wantLeaves: map[string][]string{ 1530 "parent": {"s", "d"}, 1531 }, 1532 }, { 1533 name: "parent with grouping and with extension", 1534 inModules: map[string]string{ 1535 "parent": ` 1536 module parent { 1537 prefix "p"; 1538 namespace "urn:p"; 1539 1540 import extensions { 1541 prefix "ext"; 1542 } 1543 1544 container c { 1545 ext:c-define "c's extension"; 1546 uses daughter-group { 1547 ext:u-define "uses's extension"; 1548 } 1549 } 1550 1551 grouping daughter-group { 1552 ext:g-define "daughter-group's extension"; 1553 1554 leaf l { 1555 ext:l-define "l's extension"; 1556 type string; 1557 } 1558 1559 container c2 { 1560 leaf l2 { 1561 type string; 1562 } 1563 } 1564 1565 // test nested grouping extensions. 1566 uses son-group { 1567 ext:sg-define "son-group's extension"; 1568 } 1569 } 1570 1571 grouping son-group { 1572 leaf s { 1573 ext:s-define "s's extension"; 1574 type string; 1575 } 1576 1577 } 1578 } 1579 `, 1580 "extension": ` 1581 module extensions { 1582 prefix "q"; 1583 namespace "urn:q"; 1584 1585 extension c-define { 1586 description 1587 "Takes as an argument a name string. 1588 c's extension."; 1589 argument "name"; 1590 } 1591 extension g-define { 1592 description 1593 "Takes as an argument a name string. 1594 daughter-group's extension."; 1595 argument "name"; 1596 } 1597 extension sg-define { 1598 description 1599 "Takes as an argument a name string. 1600 son-groups's extension."; 1601 argument "name"; 1602 } 1603 extension s-define { 1604 description 1605 "Takes as an argument a name string. 1606 s's extension."; 1607 argument "name"; 1608 } 1609 extension l-define { 1610 description 1611 "Takes as an argument a name string. 1612 l's extension."; 1613 argument "name"; 1614 } 1615 extension u-define { 1616 description 1617 "Takes as an argument a name string. 1618 uses's extension."; 1619 argument "name"; 1620 } 1621 } 1622 `, 1623 }, 1624 wantLeaves: map[string][]string{ 1625 "parent": {"c"}, 1626 }, 1627 customVerify: func(t *testing.T, module *Entry) { 1628 // Verify that an extension within the uses statement 1629 // and within a grouping's definition is copied to each 1630 // of the top-level nodes within the grouping, and no 1631 // one else above or below. 1632 less := cmpopts.SortSlices(func(l, r *Statement) bool { return l.Keyword < r.Keyword }) 1633 1634 if diff := cmp.Diff([]*Statement{ 1635 {Keyword: "ext:c-define", HasArgument: true, Argument: "c's extension"}, 1636 }, module.Dir["c"].Exts, cmpopts.IgnoreUnexported(Statement{}), less); diff != "" { 1637 t.Errorf("container c Exts (-want, +got):\n%s", diff) 1638 } 1639 1640 if diff := cmp.Diff([]*Statement{ 1641 {Keyword: "ext:g-define", HasArgument: true, Argument: "daughter-group's extension"}, 1642 {Keyword: "ext:l-define", HasArgument: true, Argument: "l's extension"}, 1643 {Keyword: "ext:u-define", HasArgument: true, Argument: "uses's extension"}, 1644 }, module.Dir["c"].Dir["l"].Exts, cmpopts.IgnoreUnexported(Statement{}), less); diff != "" { 1645 t.Errorf("leaf l Exts (-want, +got):\n%s", diff) 1646 } 1647 1648 if diff := cmp.Diff([]*Statement{ 1649 {Keyword: "ext:g-define", HasArgument: true, Argument: "daughter-group's extension"}, 1650 {Keyword: "ext:sg-define", HasArgument: true, Argument: "son-group's extension"}, 1651 {Keyword: "ext:s-define", HasArgument: true, Argument: "s's extension"}, 1652 {Keyword: "ext:u-define", HasArgument: true, Argument: "uses's extension"}, 1653 }, module.Dir["c"].Dir["s"].Exts, cmpopts.IgnoreUnexported(Statement{}), less); diff != "" { 1654 t.Errorf("leaf s Exts (-want, +got):\n%s", diff) 1655 } 1656 1657 if diff := cmp.Diff([]*Statement{ 1658 {Keyword: "ext:g-define", HasArgument: true, Argument: "daughter-group's extension"}, 1659 {Keyword: "ext:u-define", HasArgument: true, Argument: "uses's extension"}, 1660 }, module.Dir["c"].Dir["c2"].Exts, cmpopts.IgnoreUnexported(Statement{}), less); diff != "" { 1661 t.Errorf("container c2 Exts (-want, +got):\n%s", diff) 1662 } 1663 1664 if diff := cmp.Diff([]*Statement{}, module.Dir["c"].Dir["c2"].Dir["l2"].Exts, cmpopts.IgnoreUnexported(Statement{}), less, cmpopts.EquateEmpty()); diff != "" { 1665 t.Errorf("leaf l2 Exts (-want, +got):\n%s", diff) 1666 } 1667 }, 1668 }} 1669 1670 for _, tt := range tests { 1671 ms := NewModules() 1672 1673 ms.ParseOptions.IgnoreSubmoduleCircularDependencies = tt.inIgnoreCircDeps 1674 for n, m := range tt.inModules { 1675 if err := ms.Parse(m, n); err != nil { 1676 t.Errorf("%s: error parsing module %s, got: %v, want: nil", tt.name, n, err) 1677 } 1678 } 1679 1680 if errs := ms.Process(); len(errs) > 0 { 1681 if !tt.wantErr { 1682 t.Errorf("%s: error processing modules, got: %v, want: nil", tt.name, errs) 1683 } 1684 continue 1685 } 1686 1687 if tt.wantErr { 1688 t.Errorf("%s: did not get expected errors", tt.name) 1689 continue 1690 } 1691 1692 for m, l := range tt.wantLeaves { 1693 mod, errs := ms.GetModule(m) 1694 if len(errs) > 0 { 1695 t.Errorf("%s: cannot retrieve expected module %s, got: %v, want: nil", tt.name, m, errs) 1696 continue 1697 } 1698 1699 var leaves []string 1700 for _, n := range mod.Dir { 1701 leaves = append(leaves, n.Name) 1702 } 1703 1704 // Sort the two slices to ensure that we are comparing like with like. 1705 sort.Strings(l) 1706 sort.Strings(leaves) 1707 if !reflect.DeepEqual(l, leaves) { 1708 t.Errorf("%s: did not get expected leaves in %s, got: %v, want: %v", tt.name, m, leaves, l) 1709 } 1710 1711 if tt.customVerify != nil { 1712 tt.customVerify(t, mod) 1713 } 1714 } 1715 } 1716 } 1717 1718 func TestAnyDataAnyXML(t *testing.T) { 1719 tests := []struct { 1720 name string 1721 inModule string 1722 wantNodeKind string 1723 wantEntryKind EntryKind 1724 }{ 1725 { 1726 name: "test anyxml", 1727 wantNodeKind: "anyxml", 1728 wantEntryKind: AnyXMLEntry, 1729 inModule: `module test { 1730 namespace "urn:test"; 1731 prefix "test"; 1732 container c { 1733 anyxml data { 1734 description "anyxml"; 1735 } 1736 } 1737 }`, 1738 }, 1739 { 1740 name: "test anydata", 1741 wantNodeKind: "anydata", 1742 wantEntryKind: AnyDataEntry, 1743 inModule: `module test { 1744 namespace "urn:test"; 1745 prefix "test"; 1746 container c { 1747 anydata data { 1748 description "anydata"; 1749 } 1750 } 1751 }`, 1752 }, 1753 } 1754 for _, tt := range tests { 1755 ms := NewModules() 1756 if err := ms.Parse(tt.inModule, "test"); err != nil { 1757 t.Errorf("%s: error parsing module 'test', got: %v, want: nil", tt.name, err) 1758 } 1759 1760 if errs := ms.Process(); len(errs) > 0 { 1761 t.Errorf("%s: got module parsing errors", tt.name) 1762 for i, err := range errs { 1763 t.Errorf("%s: error #%d: %v", tt.name, i, err) 1764 } 1765 continue 1766 } 1767 1768 mod, ok := ms.Modules["test"] 1769 if !ok { 1770 t.Errorf("%s: did not find `test` module", tt.name) 1771 continue 1772 } 1773 e := ToEntry(mod) 1774 c := e.Dir["c"] 1775 if c == nil { 1776 t.Errorf("%s: did not find container c", tt.name) 1777 continue 1778 } 1779 data := c.Dir["data"] 1780 if data == nil { 1781 t.Errorf("%s: did not find leaf c/data", tt.name) 1782 continue 1783 } 1784 if got := data.Node.Kind(); got != tt.wantNodeKind { 1785 t.Errorf("%s: want Node.Kind(): %q, got: %q", tt.name, tt.wantNodeKind, got) 1786 } 1787 if got := data.Kind; got != tt.wantEntryKind { 1788 t.Errorf("%s: want Kind: %v, got: %v", tt.name, tt.wantEntryKind, got) 1789 } 1790 if got := data.Description; got != tt.wantNodeKind { 1791 t.Errorf("%s: want data.Description: %q, got: %q", tt.name, tt.wantNodeKind, got) 1792 } 1793 } 1794 } 1795 1796 func getEntry(root *Entry, path []string) *Entry { 1797 for _, elem := range path { 1798 if root = root.Dir[elem]; root == nil { 1799 break 1800 } 1801 } 1802 return root 1803 } 1804 1805 func TestActionRPC(t *testing.T) { 1806 tests := []struct { 1807 name string 1808 inModule string 1809 operationPath []string 1810 wantNodeKind string 1811 wantError string 1812 noInput bool 1813 noOutput bool 1814 }{ 1815 { 1816 name: "test action in container", 1817 wantNodeKind: "action", 1818 operationPath: []string{"c", "operation"}, 1819 inModule: `module test { 1820 namespace "urn:test"; 1821 prefix "test"; 1822 container c { 1823 action operation { 1824 description "action"; 1825 input { leaf string { type string; } } 1826 output { leaf string { type string; } } 1827 } 1828 } 1829 }`, 1830 }, 1831 1832 { 1833 name: "test action in list", 1834 wantNodeKind: "action", 1835 operationPath: []string{"list", "operation"}, 1836 inModule: `module test { 1837 namespace "urn:test"; 1838 prefix "test"; 1839 list list { 1840 action operation { 1841 description "action"; 1842 input { leaf string { type string; } } 1843 output { leaf string { type string; } } 1844 } 1845 } 1846 }`, 1847 }, 1848 1849 { 1850 name: "test action in container via grouping", 1851 wantNodeKind: "action", 1852 operationPath: []string{"c", "operation"}, 1853 inModule: `module test { 1854 namespace "urn:test"; 1855 prefix "test"; 1856 grouping g { 1857 action operation { 1858 description "action"; 1859 input { leaf string { type string; } } 1860 output { leaf string { type string; } } 1861 } 1862 } 1863 container c { uses g; } 1864 }`, 1865 }, 1866 1867 { 1868 name: "test action in list via grouping", 1869 wantNodeKind: "action", 1870 operationPath: []string{"list", "operation"}, 1871 inModule: `module test { 1872 namespace "urn:test"; 1873 prefix "test"; 1874 grouping g { 1875 action operation { 1876 description "action"; 1877 input { leaf string { type string; } } 1878 output { leaf string { type string; } } 1879 } 1880 } 1881 list list { uses g; } 1882 }`, 1883 }, 1884 1885 { 1886 name: "test rpc", 1887 wantNodeKind: "rpc", 1888 operationPath: []string{"operation"}, 1889 inModule: `module test { 1890 namespace "urn:test"; 1891 prefix "test"; 1892 rpc operation { 1893 description "rpc"; 1894 input { 1895 leaf string { type string; } 1896 } 1897 output { 1898 leaf string { type string; } 1899 } 1900 } 1901 }`, 1902 }, 1903 1904 { 1905 name: "minimal rpc", 1906 wantNodeKind: "rpc", 1907 operationPath: []string{"operation"}, 1908 inModule: `module test { 1909 namespace "urn:test"; 1910 prefix "test"; 1911 rpc operation { 1912 description "rpc"; 1913 } 1914 }`, 1915 noInput: true, 1916 noOutput: true, 1917 }, 1918 1919 { 1920 name: "input-only rpc", 1921 wantNodeKind: "rpc", 1922 operationPath: []string{"operation"}, 1923 inModule: `module test { 1924 namespace "urn:test"; 1925 prefix "test"; 1926 rpc operation { 1927 description "rpc"; 1928 input { 1929 leaf string { type string; } 1930 } 1931 } 1932 }`, 1933 noOutput: true, 1934 }, 1935 1936 { 1937 name: "output-only rpc", 1938 wantNodeKind: "rpc", 1939 operationPath: []string{"operation"}, 1940 inModule: `module test { 1941 namespace "urn:test"; 1942 prefix "test"; 1943 rpc operation { 1944 description "rpc"; 1945 output { 1946 leaf string { type string; } 1947 } 1948 } 1949 }`, 1950 noInput: true, 1951 }, 1952 1953 // test cases with errors (in module parsing) 1954 { 1955 name: "rpc not module child", 1956 wantError: "test:6:5: unknown container field: rpc", 1957 inModule: `module test { 1958 namespace "urn:test"; 1959 prefix "test"; 1960 container c { 1961 // error: "rpc" is not a valid sub-statement to "container" 1962 rpc operation; 1963 } 1964 }`, 1965 }, 1966 1967 { 1968 name: "action not valid leaf child", 1969 wantError: "test:6:5: unknown leaf field: action", 1970 inModule: `module test { 1971 namespace "urn:test"; 1972 prefix "test"; 1973 leaf l { 1974 // error: "operation" is not a valid sub-statement to "leaf" 1975 action operation; 1976 } 1977 }`, 1978 }, 1979 1980 { 1981 name: "action not valid leaf-list child", 1982 wantError: "test:6:5: unknown leaf-list field: action", 1983 inModule: `module test { 1984 namespace "urn:test"; 1985 prefix "test"; 1986 leaf-list leaf-list { 1987 // error: "operation" is not a valid sub-statement to "leaf-list" 1988 action operation; 1989 } 1990 }`, 1991 }, 1992 } 1993 for _, tt := range tests { 1994 ms := NewModules() 1995 if err := ms.Parse(tt.inModule, "test"); err != nil { 1996 if got := err.Error(); got != tt.wantError { 1997 t.Errorf("%s: error parsing module 'test', got error: %q, want: %q", tt.name, got, tt.wantError) 1998 } 1999 continue 2000 } 2001 2002 if errs := ms.Process(); len(errs) > 0 { 2003 t.Errorf("%s: got %d module parsing errors", tt.name, len(errs)) 2004 for i, err := range errs { 2005 t.Errorf("%s: error #%d: %v", tt.name, i, err) 2006 } 2007 continue 2008 } 2009 2010 mod := ms.Modules["test"] 2011 e := ToEntry(mod) 2012 if e = getEntry(e, tt.operationPath); e == nil { 2013 t.Errorf("%s: want child entry at: %v, got: nil", tt.name, tt.operationPath) 2014 continue 2015 } 2016 if got := e.Node.Kind(); got != tt.wantNodeKind { 2017 t.Errorf("%s: got `operation` node kind: %q, want: %q", tt.name, got, tt.wantNodeKind) 2018 } else if got := e.Description; got != tt.wantNodeKind { 2019 t.Errorf("%s: got `operation` Description: %q, want: %q", tt.name, got, tt.wantNodeKind) 2020 } 2021 // confirm the child RPCEntry was populated for the entry. 2022 if e.RPC == nil { 2023 t.Errorf("%s: entry at %v has nil RPC child, want: non-nil. Entry: %#v", tt.name, tt.operationPath, e) 2024 } else if !tt.noInput && e.RPC.Input == nil { 2025 t.Errorf("%s: RPCEntry has nil Input, want: non-nil. Entry: %#v", tt.name, e.RPC) 2026 } else if !tt.noOutput && e.RPC.Output == nil { 2027 t.Errorf("%s: RPCEntry has nil Output, want: non-nil. Entry: %#v", tt.name, e.RPC) 2028 } 2029 } 2030 } 2031 2032 var testIfFeatureModules = []struct { 2033 name string 2034 in string 2035 }{ 2036 { 2037 name: "if-feature.yang", 2038 in: `module if-feature { 2039 namespace "urn:if-feature"; 2040 prefix "feat"; 2041 2042 feature ft-container; 2043 feature ft-action; 2044 feature ft-anydata1; 2045 feature ft-anydata2; 2046 feature ft-anyxml; 2047 feature ft-choice; 2048 feature ft-case; 2049 feature ft-feature; 2050 feature ft-leaf; 2051 feature ft-bit; 2052 feature ft-leaf-list; 2053 feature ft-enum; 2054 feature ft-list; 2055 feature ft-notification; 2056 feature ft-rpc; 2057 feature ft-augment; 2058 feature ft-identity; 2059 feature ft-uses; 2060 feature ft-refine; 2061 feature ft-augment-uses; 2062 2063 container cont { 2064 if-feature ft-container; 2065 action act { 2066 if-feature ft-action; 2067 } 2068 } 2069 2070 anydata data { 2071 if-feature ft-anydata1; 2072 if-feature ft-anydata2; 2073 } 2074 2075 anyxml xml { 2076 if-feature ft-anyxml; 2077 } 2078 2079 choice ch { 2080 if-feature ft-choice; 2081 case cs { 2082 if-feature ft-case; 2083 } 2084 } 2085 2086 feature f { 2087 if-feature ft-feature; 2088 } 2089 2090 leaf l { 2091 if-feature ft-leaf; 2092 type bits { 2093 bit A { 2094 if-feature ft-bit; 2095 } 2096 } 2097 } 2098 2099 leaf-list ll { 2100 if-feature ft-leaf-list; 2101 type enumeration { 2102 enum zero { 2103 if-feature ft-enum; 2104 } 2105 } 2106 } 2107 2108 list ls { 2109 if-feature ft-list; 2110 } 2111 2112 notification n { 2113 if-feature ft-notification; 2114 } 2115 2116 rpc r { 2117 if-feature ft-rpc; 2118 } 2119 2120 augment "/cont" { 2121 if-feature ft-augment; 2122 uses g { 2123 if-feature ft-augment-uses; 2124 } 2125 } 2126 2127 identity id { 2128 if-feature ft-identity; 2129 } 2130 2131 uses g { 2132 if-feature ft-uses; 2133 refine rf { 2134 if-feature ft-refine; 2135 } 2136 } 2137 2138 grouping g { 2139 container gc {} 2140 } 2141 } 2142 `, 2143 }, 2144 } 2145 2146 func TestIfFeature(t *testing.T) { 2147 entryIfFeatures := func(e *Entry) []*Value { 2148 extra := e.Extra["if-feature"] 2149 if len(extra) == 0 { 2150 return nil 2151 } 2152 values := make([]*Value, len(extra)) 2153 for i, ex := range extra { 2154 values[i] = ex.(*Value) 2155 } 2156 return values 2157 } 2158 2159 featureByName := func(e *Entry, name string) *Feature { 2160 for _, f := range e.Extra["feature"] { 2161 ft := f.(*Feature) 2162 if ft.Name == name { 2163 return ft 2164 } 2165 } 2166 return nil 2167 } 2168 2169 ms := NewModules() 2170 for _, tt := range testIfFeatureModules { 2171 if err := ms.Parse(tt.in, tt.name); err != nil { 2172 t.Fatalf("could not parse module %s: %v", tt.name, err) 2173 } 2174 } 2175 2176 if errs := ms.Process(); len(errs) > 0 { 2177 t.Fatalf("could not process modules: %v", errs) 2178 } 2179 2180 mod, _ := ms.GetModule("if-feature") 2181 2182 testcases := []struct { 2183 name string 2184 inIfFeatures []*Value 2185 wantIfFeatures []string 2186 }{ 2187 // Node statements 2188 { 2189 name: "action", 2190 inIfFeatures: entryIfFeatures(mod.Dir["cont"].Dir["act"]), 2191 wantIfFeatures: []string{"ft-action"}, 2192 }, 2193 { 2194 name: "anydata", 2195 inIfFeatures: entryIfFeatures(mod.Dir["data"]), 2196 wantIfFeatures: []string{"ft-anydata1", "ft-anydata2"}, 2197 }, 2198 { 2199 name: "anyxml", 2200 inIfFeatures: entryIfFeatures(mod.Dir["xml"]), 2201 wantIfFeatures: []string{"ft-anyxml"}, 2202 }, 2203 { 2204 name: "case", 2205 inIfFeatures: entryIfFeatures(mod.Dir["ch"].Dir["cs"]), 2206 wantIfFeatures: []string{"ft-case"}, 2207 }, 2208 { 2209 name: "choice", 2210 inIfFeatures: entryIfFeatures(mod.Dir["ch"]), 2211 wantIfFeatures: []string{"ft-choice"}, 2212 }, 2213 { 2214 name: "container", 2215 inIfFeatures: entryIfFeatures(mod.Dir["cont"]), 2216 wantIfFeatures: []string{"ft-container"}, 2217 }, 2218 { 2219 name: "feature", 2220 inIfFeatures: featureByName(mod, "f").IfFeature, 2221 wantIfFeatures: []string{"ft-feature"}, 2222 }, 2223 { 2224 name: "leaf", 2225 inIfFeatures: entryIfFeatures(mod.Dir["l"]), 2226 wantIfFeatures: []string{"ft-leaf"}, 2227 }, 2228 { 2229 name: "leaf-list", 2230 inIfFeatures: entryIfFeatures(mod.Dir["ll"]), 2231 wantIfFeatures: []string{"ft-leaf-list"}, 2232 }, 2233 { 2234 name: "list", 2235 inIfFeatures: entryIfFeatures(mod.Dir["ls"]), 2236 wantIfFeatures: []string{"ft-list"}, 2237 }, 2238 { 2239 name: "notification", 2240 inIfFeatures: entryIfFeatures(mod.Dir["n"]), 2241 wantIfFeatures: []string{"ft-notification"}, 2242 }, 2243 { 2244 name: "rpc", 2245 inIfFeatures: entryIfFeatures(mod.Dir["r"]), 2246 wantIfFeatures: []string{"ft-rpc"}, 2247 }, 2248 // Other statements 2249 { 2250 name: "augment", 2251 inIfFeatures: entryIfFeatures(mod.Dir["cont"].Augmented[0]), 2252 wantIfFeatures: []string{"ft-augment"}, 2253 }, 2254 { 2255 name: "bit", 2256 inIfFeatures: mod.Dir["l"].Node.(*Leaf).Type.Bit[0].IfFeature, 2257 wantIfFeatures: []string{"ft-bit"}, 2258 }, 2259 { 2260 name: "enum", 2261 inIfFeatures: mod.Dir["ll"].Node.(*Leaf).Type.Enum[0].IfFeature, 2262 wantIfFeatures: []string{"ft-enum"}, 2263 }, 2264 { 2265 name: "identity", 2266 inIfFeatures: mod.Identities[0].IfFeature, 2267 wantIfFeatures: []string{"ft-identity"}, 2268 }, 2269 { 2270 name: "refine", 2271 inIfFeatures: ms.Modules["if-feature"].Uses[0].Refine[0].IfFeature, 2272 wantIfFeatures: []string{"ft-refine"}, 2273 }, 2274 { 2275 name: "uses", 2276 inIfFeatures: ms.Modules["if-feature"].Uses[0].IfFeature, 2277 wantIfFeatures: []string{"ft-uses"}, 2278 }, 2279 { 2280 // Verify that if-feature field defined in "uses" is correctly propagated to container 2281 name: "uses", 2282 inIfFeatures: entryIfFeatures(mod.Dir["gc"]), 2283 wantIfFeatures: []string{"ft-uses"}, 2284 }, 2285 { 2286 // Verify that if-feature field defined in "augment" and in "augment > uses" is correctly propagated to container 2287 name: "augment-uses", 2288 inIfFeatures: entryIfFeatures(mod.Dir["cont"].Dir["gc"]), 2289 wantIfFeatures: []string{"ft-augment-uses", "ft-augment"}, 2290 }, 2291 } 2292 2293 for _, tc := range testcases { 2294 t.Run(tc.name, func(t *testing.T) { 2295 var names []string 2296 for _, f := range tc.inIfFeatures { 2297 names = append(names, f.Name) 2298 } 2299 2300 if !reflect.DeepEqual(names, tc.wantIfFeatures) { 2301 t.Errorf("%s: did not get expected if-features, got %v, want %v", tc.name, names, tc.wantIfFeatures) 2302 } 2303 }) 2304 } 2305 } 2306 2307 var testNotificationModules = []struct { 2308 name string 2309 in string 2310 }{ 2311 { 2312 name: "notification.yang", 2313 in: `module notification { 2314 namespace "urn:notification"; 2315 prefix "n"; 2316 2317 notification n {} 2318 2319 grouping g { 2320 notification g-n {} 2321 } 2322 2323 container cont { 2324 notification cont-n {} 2325 } 2326 2327 list ls { 2328 notification ls-n {} 2329 uses g; 2330 } 2331 2332 augment "/cont" { 2333 notification aug-n {} 2334 } 2335 } 2336 `, 2337 }, 2338 } 2339 2340 func TestNotification(t *testing.T) { 2341 ms := NewModules() 2342 for _, tt := range testNotificationModules { 2343 if err := ms.Parse(tt.in, tt.name); err != nil { 2344 t.Fatalf("could not parse module %s: %v", tt.name, err) 2345 } 2346 } 2347 2348 if errs := ms.Process(); len(errs) > 0 { 2349 t.Fatalf("could not process modules: %v", errs) 2350 } 2351 2352 mod, _ := ms.GetModule("notification") 2353 2354 testcases := []struct { 2355 name string 2356 wantPath []string 2357 }{ 2358 { 2359 name: "module", 2360 wantPath: []string{"n"}, 2361 }, 2362 { 2363 name: "container", 2364 wantPath: []string{"cont", "cont-n"}, 2365 }, 2366 { 2367 name: "list", 2368 wantPath: []string{"ls", "ls-n"}, 2369 }, 2370 { 2371 name: "grouping", 2372 wantPath: []string{"ls", "g-n"}, 2373 }, 2374 { 2375 name: "augment", 2376 wantPath: []string{"cont", "aug-n"}, 2377 }, 2378 } 2379 2380 for _, tc := range testcases { 2381 t.Run(tc.name, func(t *testing.T) { 2382 if e := getEntry(mod, tc.wantPath); e == nil || e.Node.Kind() != "notification" { 2383 t.Errorf("%s: want notification entry at: %v, got: %+v", tc.name, tc.wantPath, e) 2384 } 2385 }) 2386 } 2387 } 2388 2389 // addTreeE takes an input Entry and appends it to a directory, keyed by path, to the Entry. 2390 // If the Entry has children, they are appended to the directory recursively. Used in test 2391 // cases where a path is to be referred to. 2392 func addTreeE(e *Entry, dir map[string]*Entry) { 2393 for _, ch := range e.Dir { 2394 dir[ch.Path()] = ch 2395 if ch.Dir != nil { 2396 addTreeE(ch, dir) 2397 } 2398 } 2399 } 2400 2401 func TestEntryFind(t *testing.T) { 2402 tests := []struct { 2403 name string 2404 inModules map[string]string 2405 inBaseEntryPath string 2406 wantEntryPath map[string]string // keyed on path to find, with path expected as value. 2407 wantError string 2408 }{{ 2409 name: "intra module find", 2410 inModules: map[string]string{ 2411 "test.yang": ` 2412 module test { 2413 prefix "t"; 2414 namespace "urn:t"; 2415 2416 leaf a { type string; } 2417 leaf b { type string; } 2418 2419 container c { leaf d { type string; } } 2420 2421 rpc rpc1 { 2422 input { leaf input1 { type string; } } 2423 } 2424 2425 container e { 2426 action operation { 2427 description "action"; 2428 input { leaf input1 { type string; } } 2429 output { leaf output1 { type string; } } 2430 } 2431 } 2432 2433 } 2434 `, 2435 }, 2436 inBaseEntryPath: "/test/a", 2437 wantEntryPath: map[string]string{ 2438 // Absolute path with no prefixes. 2439 "/b": "/test/b", 2440 // Relative path with no prefixes. 2441 "../b": "/test/b", 2442 // Absolute path with prefixes. 2443 "/t:b": "/test/b", 2444 // Relative path with prefixes. 2445 "../t:b": "/test/b", 2446 // Find within a directory. 2447 "/c/d": "/test/c/d", 2448 // Find within a directory specified relatively. 2449 "../c/d": "/test/c/d", 2450 // Find within a relative directory with prefixes. 2451 "../t:c/t:d": "/test/c/d", 2452 "../t:c/d": "/test/c/d", 2453 "../c/t:d": "/test/c/d", 2454 // Find within an absolute directory with prefixes. 2455 "/t:c/d": "/test/c/d", 2456 "/c/t:d": "/test/c/d", 2457 "../t:rpc1/input": "/test/rpc1/input", 2458 "/t:rpc1/input": "/test/rpc1/input", 2459 "/t:rpc1/t:input": "/test/rpc1/input", 2460 "/t:e/operation/input": "/test/e/operation/input", 2461 "/t:e/operation/output": "/test/e/operation/output", 2462 "/t:e/t:operation/t:input": "/test/e/operation/input", 2463 "/t:e/t:operation/t:output": "/test/e/operation/output", 2464 }, 2465 }, { 2466 name: "submodule find", 2467 inModules: map[string]string{ 2468 "test.yang": ` 2469 module test { 2470 prefix "t"; 2471 namespace "urn:t"; 2472 2473 include test1; 2474 2475 leaf a { type string; } 2476 leaf b { type string; } 2477 2478 container c { leaf d { type string; } } 2479 2480 rpc rpc1 { 2481 input { leaf input1 { type string; } } 2482 } 2483 2484 container e { 2485 action operation { 2486 description "action"; 2487 input { leaf input1 { type string; } } 2488 output { leaf output1 { type string; } } 2489 } 2490 } 2491 2492 } 2493 `, 2494 "test1.yang": ` 2495 submodule test1 { 2496 belongs-to test { 2497 prefix "t"; 2498 } 2499 2500 leaf d { type string; } 2501 } 2502 `, 2503 }, 2504 inBaseEntryPath: "/test/d", 2505 wantEntryPath: map[string]string{ 2506 // Absolute path with no prefixes. 2507 "/b": "/test/b", 2508 // Relative path with no prefixes. 2509 "../b": "/test/b", 2510 // Absolute path with prefixes. 2511 "/t:b": "/test/b", 2512 // Relative path with prefixes. 2513 "../t:b": "/test/b", 2514 // Find within a directory. 2515 "/c/d": "/test/c/d", 2516 // Find within a directory specified relatively. 2517 "../c/d": "/test/c/d", 2518 // Find within a relative directory with prefixes. 2519 "../t:c/t:d": "/test/c/d", 2520 "../t:c/d": "/test/c/d", 2521 "../c/t:d": "/test/c/d", 2522 // Find within an absolute directory with prefixes. 2523 "/t:c/d": "/test/c/d", 2524 "/c/t:d": "/test/c/d", 2525 "../t:rpc1/input": "/test/rpc1/input", 2526 "/t:rpc1/input": "/test/rpc1/input", 2527 "/t:rpc1/t:input": "/test/rpc1/input", 2528 "/t:e/operation/input": "/test/e/operation/input", 2529 "/t:e/operation/output": "/test/e/operation/output", 2530 "/t:e/t:operation/t:input": "/test/e/operation/input", 2531 "/t:e/t:operation/t:output": "/test/e/operation/output", 2532 }, 2533 }, { 2534 name: "inter-module find", 2535 inModules: map[string]string{ 2536 "test.yang": ` 2537 module test { 2538 prefix "t"; 2539 namespace "urn:t"; 2540 2541 import foo { prefix foo; } 2542 import bar { prefix baz; } 2543 2544 leaf ctx { type string; } 2545 leaf other { type string; } 2546 leaf conflict { type string; } 2547 }`, 2548 "foo.yang": ` 2549 module foo { 2550 prefix "foo"; // matches the import above 2551 namespace "urn:foo"; 2552 2553 container bar { 2554 leaf baz { type string; } 2555 } 2556 2557 leaf conflict { type string; } 2558 }`, 2559 "bar.yang": ` 2560 module bar { 2561 prefix "bar"; // does not match import in test 2562 namespace "urn:b"; 2563 2564 container fish { 2565 leaf chips { type string; } 2566 } 2567 2568 leaf conflict { type string; } 2569 }`, 2570 }, 2571 inBaseEntryPath: "/test/ctx", 2572 wantEntryPath: map[string]string{ 2573 // Check we can still do intra module lookups 2574 "../other": "/test/other", 2575 "/other": "/test/other", 2576 "/foo:bar/foo:baz": "/foo/bar/baz", 2577 // Technically partially prefixed paths to remote modules are 2578 // not legal - check whether we can resolve them. 2579 "/foo:bar/baz": "/foo/bar/baz", 2580 // With mismatched prefixes. 2581 "/baz:fish/baz:chips": "/bar/fish/chips", 2582 // With conflicting node names 2583 "/conflict": "/test/conflict", 2584 "/foo:conflict": "/foo/conflict", 2585 "/baz:conflict": "/bar/conflict", 2586 "/t:conflict": "/test/conflict", 2587 }, 2588 }} 2589 2590 for _, tt := range tests { 2591 ms := NewModules() 2592 var errs []error 2593 for n, m := range tt.inModules { 2594 if err := ms.Parse(m, n); err != nil { 2595 errs = append(errs, err) 2596 } 2597 } 2598 2599 if len(errs) > 0 { 2600 t.Errorf("%s: ms.Parse(), got unexpected error parsing input modules: %v", tt.name, errs) 2601 continue 2602 } 2603 2604 if errs := ms.Process(); len(errs) > 0 { 2605 t.Errorf("%s: ms.Process(), got unexpected error processing entries: %v", tt.name, errs) 2606 continue 2607 } 2608 2609 dir := map[string]*Entry{} 2610 for _, m := range ms.Modules { 2611 addTreeE(ToEntry(m), dir) 2612 } 2613 2614 if _, ok := dir[tt.inBaseEntryPath]; !ok { 2615 t.Errorf("%s: could not find entry %s within the dir: %v", tt.name, tt.inBaseEntryPath, dir) 2616 } 2617 2618 for path, want := range tt.wantEntryPath { 2619 got := dir[tt.inBaseEntryPath].Find(path) 2620 if got.Path() != want { 2621 t.Errorf("%s: (entry %s).Find(%s), did not find path, got: %v, want: %v, errors: %v", tt.name, dir[tt.inBaseEntryPath].Path(), path, got.Path(), want, dir[tt.inBaseEntryPath].Errors) 2622 } 2623 } 2624 } 2625 } 2626 2627 func TestEntryTypes(t *testing.T) { 2628 leafSchema := &Entry{Name: "leaf-schema", Kind: LeafEntry, Type: &YangType{Kind: Ystring}} 2629 2630 containerSchema := &Entry{ 2631 Name: "container-schema", 2632 Kind: DirectoryEntry, 2633 Dir: map[string]*Entry{ 2634 "config": { 2635 Dir: map[string]*Entry{ 2636 "leaf1": { 2637 Kind: LeafEntry, 2638 Name: "Leaf1Name", 2639 Type: &YangType{Kind: Ystring}, 2640 }, 2641 }, 2642 }, 2643 }, 2644 } 2645 2646 emptyContainerSchema := &Entry{ 2647 Name: "empty-container-schema", 2648 Kind: DirectoryEntry, 2649 } 2650 2651 leafListSchema := &Entry{ 2652 Kind: LeafEntry, 2653 ListAttr: &ListAttr{MinElements: 0}, 2654 Type: &YangType{Kind: Ystring}, 2655 Name: "leaf-list-schema", 2656 } 2657 2658 listSchema := &Entry{ 2659 Name: "list-schema", 2660 Kind: DirectoryEntry, 2661 ListAttr: &ListAttr{MinElements: 0}, 2662 Dir: map[string]*Entry{ 2663 "leaf-name": { 2664 Kind: LeafEntry, 2665 Name: "LeafName", 2666 Type: &YangType{Kind: Ystring}, 2667 }, 2668 }, 2669 } 2670 2671 choiceSchema := &Entry{ 2672 Kind: ChoiceEntry, 2673 Name: "Choice1Name", 2674 Dir: map[string]*Entry{ 2675 "case1": { 2676 Kind: CaseEntry, 2677 Name: "case1", 2678 Dir: map[string]*Entry{ 2679 "case1-leaf1": { 2680 Kind: LeafEntry, 2681 Name: "Case1Leaf1", 2682 Type: &YangType{Kind: Ystring}, 2683 }, 2684 }, 2685 }, 2686 }, 2687 } 2688 2689 type SchemaType string 2690 const ( 2691 Leaf SchemaType = "Leaf" 2692 Container SchemaType = "Container" 2693 LeafList SchemaType = "LeafList" 2694 List SchemaType = "List" 2695 Choice SchemaType = "Choice" 2696 Case SchemaType = "Case" 2697 ) 2698 2699 tests := []struct { 2700 desc string 2701 schema *Entry 2702 wantType SchemaType 2703 }{ 2704 { 2705 desc: "leaf", 2706 schema: leafSchema, 2707 wantType: Leaf, 2708 }, 2709 { 2710 desc: "container", 2711 schema: containerSchema, 2712 wantType: Container, 2713 }, 2714 { 2715 desc: "empty container", 2716 schema: emptyContainerSchema, 2717 wantType: Container, 2718 }, 2719 { 2720 desc: "leaf-list", 2721 schema: leafListSchema, 2722 wantType: LeafList, 2723 }, 2724 { 2725 desc: "list", 2726 schema: listSchema, 2727 wantType: List, 2728 }, 2729 { 2730 desc: "choice", 2731 schema: choiceSchema, 2732 wantType: Choice, 2733 }, 2734 { 2735 desc: "case", 2736 schema: choiceSchema.Dir["case1"], 2737 wantType: Case, 2738 }, 2739 } 2740 2741 for _, tt := range tests { 2742 gotm := map[SchemaType]bool{ 2743 Leaf: tt.schema.IsLeaf(), 2744 Container: tt.schema.IsContainer(), 2745 LeafList: tt.schema.IsLeafList(), 2746 List: tt.schema.IsList(), 2747 Choice: tt.schema.IsChoice(), 2748 Case: tt.schema.IsCase(), 2749 } 2750 2751 for stype, got := range gotm { 2752 if want := (stype == tt.wantType); got != want { 2753 t.Errorf("%s: got Is%v? %t, want Is%v? %t", tt.desc, stype, got, stype, want) 2754 } 2755 } 2756 } 2757 } 2758 2759 func TestFixChoice(t *testing.T) { 2760 choiceEntry := &Entry{ 2761 Name: "choiceEntry", 2762 Kind: ChoiceEntry, 2763 Dir: map[string]*Entry{ 2764 "unnamedAnyDataCase": { 2765 Name: "unnamedAnyDataCase", 2766 Kind: AnyDataEntry, 2767 Node: &AnyData{ 2768 Parent: &Container{ 2769 Name: "AnyDataParentNode", 2770 }, 2771 Name: "unnamedAnyDataCase", 2772 Source: &Statement{ 2773 Keyword: "anyData-keyword", 2774 HasArgument: true, 2775 Argument: "anyData-argument", 2776 statements: nil, 2777 }, 2778 Extensions: []*Statement{ 2779 { 2780 Keyword: "anyData-extension", 2781 HasArgument: true, 2782 Argument: "anyData-extension-arg", 2783 statements: nil, 2784 }, 2785 }, 2786 }, 2787 }, 2788 "unnamedAnyXMLCase": { 2789 Name: "unnamedAnyXMLCase", 2790 Kind: AnyXMLEntry, 2791 Node: &AnyXML{ 2792 Parent: &Container{ 2793 Name: "AnyXMLParentNode", 2794 }, 2795 Name: "unnamedAnyXMLCase", 2796 Source: &Statement{ 2797 Keyword: "anyXML-keyword", 2798 HasArgument: true, 2799 Argument: "anyXML-argument", 2800 statements: nil, 2801 }, 2802 Extensions: []*Statement{ 2803 { 2804 Keyword: "anyXML-extension", 2805 HasArgument: true, 2806 Argument: "anyXML-extension-arg", 2807 statements: nil, 2808 }, 2809 }, 2810 }, 2811 }, 2812 "unnamedContainerCase": { 2813 Name: "unnamedContainerCase", 2814 Kind: DirectoryEntry, 2815 Node: &Container{ 2816 Parent: &Container{ 2817 Name: "AnyContainerNode", 2818 }, 2819 Name: "unnamedContainerCase", 2820 Source: &Statement{ 2821 Keyword: "container-keyword", 2822 HasArgument: true, 2823 Argument: "container-argument", 2824 statements: nil, 2825 }, 2826 Extensions: []*Statement{ 2827 { 2828 Keyword: "container-extension", 2829 HasArgument: true, 2830 Argument: "container-extension-arg", 2831 statements: nil, 2832 }, 2833 }, 2834 }, 2835 }, 2836 "unnamedLeafCase": { 2837 Name: "unnamedLeafCase", 2838 Kind: LeafEntry, 2839 Node: &Leaf{ 2840 Parent: &Container{ 2841 Name: "leafParentNode", 2842 }, 2843 Name: "unnamedLeafCase", 2844 Source: &Statement{ 2845 Keyword: "leaf-keyword", 2846 HasArgument: true, 2847 Argument: "leaf-argument", 2848 statements: nil, 2849 }, 2850 Extensions: []*Statement{ 2851 { 2852 Keyword: "leaf-extension", 2853 HasArgument: true, 2854 Argument: "leaf-extension-arg", 2855 statements: nil, 2856 }, 2857 }, 2858 }, 2859 }, 2860 "unnamedLeaf-ListCase": { 2861 Name: "unnamedLeaf-ListCase", 2862 Kind: LeafEntry, 2863 Node: &LeafList{ 2864 Parent: &Container{ 2865 Name: "LeafListNode", 2866 }, 2867 Name: "unnamedLeaf-ListCase", 2868 Source: &Statement{ 2869 Keyword: "leaflist-keyword", 2870 HasArgument: true, 2871 Argument: "leaflist-argument", 2872 statements: nil, 2873 }, 2874 Extensions: []*Statement{ 2875 { 2876 Keyword: "leaflist-extension", 2877 HasArgument: true, 2878 Argument: "leaflist-extension-arg", 2879 statements: nil, 2880 }, 2881 }, 2882 }, 2883 }, 2884 "unnamedListCase": { 2885 Name: "unnamedListCase", 2886 Kind: DirectoryEntry, 2887 Node: &List{ 2888 Parent: &Container{ 2889 Name: "ListNode", 2890 }, 2891 Name: "unnamedListCase", 2892 Source: &Statement{ 2893 Keyword: "list-keyword", 2894 HasArgument: true, 2895 Argument: "list-argument", 2896 statements: nil, 2897 }, 2898 Extensions: []*Statement{ 2899 { 2900 Keyword: "list-extension", 2901 HasArgument: true, 2902 Argument: "list-extension-arg", 2903 statements: nil, 2904 }, 2905 }, 2906 }, 2907 }, 2908 }, 2909 } 2910 2911 choiceEntry.FixChoice() 2912 2913 for _, e := range []string{"AnyData", "AnyXML", "Container", 2914 "Leaf", "Leaf-List", "List"} { 2915 entryName := "unnamed" + e + "Case" 2916 t.Run(entryName, func(t *testing.T) { 2917 2918 insertedCase := choiceEntry.Dir[entryName] 2919 originalCase := insertedCase.Dir[entryName] 2920 2921 insertedNode := insertedCase.Node 2922 if insertedNode.Kind() != "case" { 2923 t.Errorf("Got inserted node type %s, expected case", 2924 insertedNode.Kind()) 2925 } 2926 2927 originalNode := originalCase.Node 2928 if originalNode.Kind() != strings.ToLower(e) { 2929 t.Errorf("Got original node type %s, expected %s", 2930 originalNode.Kind(), strings.ToLower(e)) 2931 } 2932 2933 if insertedNode.ParentNode() != originalNode.ParentNode() { 2934 t.Errorf("Got inserted node's parent node %v, expected %v", 2935 insertedNode.ParentNode(), originalNode.ParentNode()) 2936 } 2937 2938 if insertedNode.NName() != originalNode.NName() { 2939 t.Errorf("Got inserted node's name %s, expected %s", 2940 insertedNode.NName(), originalNode.NName()) 2941 } 2942 2943 if insertedNode.Statement() != originalNode.Statement() { 2944 t.Errorf("Got inserted node's statement %v, expected %v", 2945 insertedNode.Statement(), originalNode.Statement()) 2946 } 2947 2948 if len(insertedNode.Exts()) != len(originalNode.Exts()) { 2949 t.Errorf("Got inserted node extensions slice len %d, expected %v", 2950 len(insertedNode.Exts()), len(originalNode.Exts())) 2951 } 2952 2953 for i, e := range insertedNode.Exts() { 2954 if e != originalNode.Exts()[i] { 2955 t.Errorf("Got inserted node's extension %v at index %d, expected %v", 2956 e, i, originalNode.Exts()[i]) 2957 } 2958 } 2959 }) 2960 } 2961 } 2962 2963 func mustReadFile(path string) string { 2964 s, err := ioutil.ReadFile(path) 2965 if err != nil { 2966 panic(err) 2967 } 2968 return string(s) 2969 } 2970 2971 func TestDeviation(t *testing.T) { 2972 type deviationTest struct { 2973 path string 2974 entry *Entry // entry is the entry that is wanted at a particular path, if a field is left as nil, it is not checked. 2975 } 2976 tests := []struct { 2977 desc string 2978 inFiles map[string]string 2979 inParseOptions Options 2980 wants map[string][]deviationTest 2981 wantParseErrSubstring string 2982 wantProcessErrSubstring string 2983 }{{ 2984 desc: "deviation with add", 2985 inFiles: map[string]string{"deviate": mustReadFile(filepath.Join("testdata", "deviate.yang"))}, 2986 wants: map[string][]deviationTest{ 2987 "deviate": {{ 2988 path: "/target/add/config", 2989 entry: &Entry{ 2990 Config: TSFalse, 2991 }, 2992 }, { 2993 path: "/target/add/default", 2994 entry: &Entry{ 2995 Default: []string{"a default value"}, 2996 }, 2997 }, { 2998 path: "/target/add/default-typedef", 2999 entry: &Entry{ 3000 Default: nil, 3001 }, 3002 }, { 3003 path: "/target/add/default-list", 3004 entry: &Entry{ 3005 Default: []string{"foo", "bar", "foo"}, 3006 }, 3007 }, { 3008 path: "/target/add/default-list-typedef-default", 3009 entry: &Entry{ 3010 Default: nil, 3011 }, 3012 }, { 3013 path: "/target/add/mandatory", 3014 entry: &Entry{ 3015 Mandatory: TSTrue, 3016 }, 3017 }, { 3018 path: "/target/add/min-elements", 3019 entry: &Entry{ 3020 ListAttr: &ListAttr{ 3021 MinElements: 42, 3022 }, 3023 deviatePresence: deviationPresence{ 3024 hasMinElements: true, 3025 }, 3026 }, 3027 }, { 3028 path: "/target/add/max-elements", 3029 entry: &Entry{ 3030 ListAttr: &ListAttr{ 3031 MaxElements: 42, 3032 }, 3033 deviatePresence: deviationPresence{ 3034 hasMaxElements: true, 3035 }, 3036 }, 3037 }, { 3038 path: "/target/add/max-and-min-elements", 3039 entry: &Entry{ 3040 ListAttr: &ListAttr{ 3041 MinElements: 42, 3042 MaxElements: 42, 3043 }, 3044 deviatePresence: deviationPresence{ 3045 hasMinElements: true, 3046 hasMaxElements: true, 3047 }, 3048 }, 3049 }, { 3050 path: "/target/add/units", 3051 entry: &Entry{ 3052 Units: "fish per second", 3053 }, 3054 }}, 3055 }, 3056 }, { 3057 desc: "error case - deviation add that already has a default", 3058 inFiles: map[string]string{ 3059 "deviate": ` 3060 module deviate { 3061 prefix "d"; 3062 namespace "urn:d"; 3063 3064 leaf a { 3065 type string; 3066 default "fish"; 3067 } 3068 3069 deviation /a { 3070 deviate add { 3071 default "fishsticks"; 3072 } 3073 } 3074 }`, 3075 }, 3076 wantProcessErrSubstring: "already has a default value", 3077 }, { 3078 desc: "error case - deviate type not recognized", 3079 inFiles: map[string]string{ 3080 "deviate": ` 3081 module deviate { 3082 prefix "d"; 3083 namespace "urn:d"; 3084 3085 leaf a { type string; } 3086 3087 deviation /a { 3088 deviate shrink { 3089 max-elements 42; 3090 } 3091 } 3092 }`, 3093 }, 3094 wantProcessErrSubstring: "unknown deviation type", 3095 }, { 3096 desc: "error case - deviation add max-element to non-list", 3097 inFiles: map[string]string{ 3098 "deviate": ` 3099 module deviate { 3100 prefix "d"; 3101 namespace "urn:d"; 3102 3103 leaf a { type string; } 3104 3105 deviation /a { 3106 deviate add { 3107 max-elements 42; 3108 } 3109 } 3110 }`, 3111 }, 3112 wantProcessErrSubstring: "tried to deviate max-elements on a non-list type", 3113 }, { 3114 desc: "error case - deviation add min elements to non-list", 3115 inFiles: map[string]string{ 3116 "deviate": ` 3117 module deviate { 3118 prefix "d"; 3119 namespace "urn:d"; 3120 3121 leaf a { type string; } 3122 3123 deviation /a { 3124 deviate add { 3125 min-elements 42; 3126 } 3127 } 3128 }`, 3129 }, 3130 wantProcessErrSubstring: "tried to deviate min-elements on a non-list type", 3131 }, { 3132 desc: "error case - deviation delete max-element on non-list", 3133 inFiles: map[string]string{ 3134 "deviate": ` 3135 module deviate { 3136 prefix "d"; 3137 namespace "urn:d"; 3138 3139 leaf a { type string; } 3140 3141 deviation /a { 3142 deviate delete { 3143 max-elements 42; 3144 } 3145 } 3146 }`, 3147 }, 3148 wantProcessErrSubstring: "tried to deviate max-elements on a non-list type", 3149 }, { 3150 desc: "error case - deviation delete min elements on non-list", 3151 inFiles: map[string]string{ 3152 "deviate": ` 3153 module deviate { 3154 prefix "d"; 3155 namespace "urn:d"; 3156 3157 leaf a { type string; } 3158 3159 deviation /a { 3160 deviate delete { 3161 min-elements 42; 3162 } 3163 } 3164 }`, 3165 }, 3166 wantProcessErrSubstring: "tried to deviate min-elements on a non-list type", 3167 }, { 3168 desc: "deviation - not supported", 3169 inFiles: map[string]string{"deviate": mustReadFile(filepath.Join("testdata", "deviate-notsupported.yang"))}, 3170 wants: map[string][]deviationTest{ 3171 "deviate": {{ 3172 path: "/target", 3173 }, { 3174 path: "/target-list", 3175 }, { 3176 path: "/a-leaf", 3177 }, { 3178 path: "/a-leaflist", 3179 }, { 3180 path: "survivor", 3181 entry: &Entry{Name: "survivor"}, 3182 }}, 3183 }, 3184 }, { 3185 desc: "deviation - not supported but ignored by option", 3186 inFiles: map[string]string{"deviate": mustReadFile(filepath.Join("testdata", "deviate-notsupported.yang"))}, 3187 inParseOptions: Options{ 3188 DeviateOptions: DeviateOptions{ 3189 IgnoreDeviateNotSupported: true, 3190 }, 3191 }, 3192 wants: map[string][]deviationTest{ 3193 "deviate": {{ 3194 path: "/target", 3195 entry: &Entry{Name: "target"}, 3196 }, { 3197 path: "/target-list", 3198 entry: &Entry{Name: "target-list"}, 3199 }, { 3200 path: "/a-leaf", 3201 entry: &Entry{Name: "a-leaf"}, 3202 }, { 3203 path: "/a-leaflist", 3204 entry: &Entry{Name: "a-leaflist"}, 3205 }, { 3206 path: "survivor", 3207 entry: &Entry{Name: "survivor"}, 3208 }}, 3209 }, 3210 }, { 3211 desc: "deviation removing non-existent node", 3212 inFiles: map[string]string{ 3213 "deviate": ` 3214 module deviate { 3215 prefix "d"; 3216 namespace "urn:d"; 3217 3218 deviation /a/b/c { 3219 deviate not-supported; 3220 } 3221 } 3222 `, 3223 }, 3224 wantProcessErrSubstring: "cannot find target node to deviate", 3225 }, { 3226 desc: "deviation not supported across modules", 3227 inFiles: map[string]string{ 3228 "source": ` 3229 module source { 3230 prefix "s"; 3231 namespace "urn:s"; 3232 3233 leaf a { type string; } 3234 leaf b { type string; } 3235 }`, 3236 "deviation": ` 3237 module deviation { 3238 prefix "d"; 3239 namespace "urn:d"; 3240 3241 import source { prefix s; } 3242 3243 deviation /s:a { 3244 deviate not-supported; 3245 } 3246 }`, 3247 }, 3248 wants: map[string][]deviationTest{ 3249 "source": {{ 3250 path: "/a", 3251 }, { 3252 path: "/b", 3253 entry: &Entry{}, 3254 }}, 3255 }, 3256 }, { 3257 desc: "deviation with replace", 3258 inFiles: map[string]string{"deviate": mustReadFile(filepath.Join("testdata", "deviate-replace.yang"))}, 3259 wants: map[string][]deviationTest{ 3260 "deviate": {{ 3261 path: "/target/replace/config", 3262 entry: &Entry{ 3263 Config: TSFalse, 3264 }, 3265 }, { 3266 path: "/target/replace/default", 3267 entry: &Entry{ 3268 Default: []string{"a default value"}, 3269 }, 3270 }, { 3271 path: "/target/replace/default-list", 3272 entry: &Entry{ 3273 Default: []string{"nematodes"}, 3274 }, 3275 }, { 3276 path: "/target/replace/mandatory", 3277 entry: &Entry{ 3278 Mandatory: TSTrue, 3279 }, 3280 }, { 3281 path: "/target/replace/min-elements", 3282 entry: &Entry{ 3283 ListAttr: &ListAttr{ 3284 MinElements: 42, 3285 }, 3286 deviatePresence: deviationPresence{ 3287 hasMinElements: true, 3288 }, 3289 }, 3290 }, { 3291 path: "/target/replace/max-elements", 3292 entry: &Entry{ 3293 ListAttr: &ListAttr{ 3294 MaxElements: 42, 3295 }, 3296 deviatePresence: deviationPresence{ 3297 hasMaxElements: true, 3298 }, 3299 }, 3300 }, { 3301 path: "/target/replace/max-and-min-elements", 3302 entry: &Entry{ 3303 ListAttr: &ListAttr{ 3304 MinElements: 42, 3305 MaxElements: 42, 3306 }, 3307 deviatePresence: deviationPresence{ 3308 hasMinElements: true, 3309 hasMaxElements: true, 3310 }, 3311 }, 3312 }, { 3313 path: "/target/replace/units", 3314 entry: &Entry{ 3315 Units: "fish per second", 3316 }, 3317 }, { 3318 path: "/target/replace/type", 3319 entry: &Entry{ 3320 Type: &YangType{ 3321 Name: "uint16", 3322 Kind: Yuint16, 3323 }, 3324 }, 3325 }}, 3326 }, 3327 }, { 3328 desc: "deviation with delete", 3329 inFiles: map[string]string{"deviate": mustReadFile(filepath.Join("testdata", "deviate-delete.yang"))}, 3330 wants: map[string][]deviationTest{ 3331 "deviate": {{ 3332 path: "/target/delete/config", 3333 entry: &Entry{ 3334 Config: TSUnset, 3335 }, 3336 }, { 3337 path: "/target/delete/default", 3338 entry: &Entry{}, 3339 }, { 3340 path: "/target/delete/mandatory", 3341 entry: &Entry{ 3342 Mandatory: TSUnset, 3343 }, 3344 }, { 3345 path: "/target/delete/min-elements", 3346 entry: &Entry{ 3347 ListAttr: &ListAttr{ 3348 MinElements: 0, 3349 }, 3350 deviatePresence: deviationPresence{ 3351 hasMinElements: true, 3352 }, 3353 }, 3354 }, { 3355 path: "/target/delete/max-elements", 3356 entry: &Entry{ 3357 ListAttr: &ListAttr{ 3358 MaxElements: math.MaxUint64, 3359 }, 3360 deviatePresence: deviationPresence{ 3361 hasMaxElements: true, 3362 }, 3363 }, 3364 }, { 3365 path: "/target/delete/max-and-min-elements", 3366 entry: &Entry{ 3367 ListAttr: &ListAttr{ 3368 MinElements: 0, 3369 MaxElements: math.MaxUint64, 3370 }, 3371 deviatePresence: deviationPresence{ 3372 hasMinElements: true, 3373 hasMaxElements: true, 3374 }, 3375 }, 3376 }, { 3377 path: "/target/delete/units", 3378 entry: &Entry{ 3379 Units: "", 3380 }, 3381 }}, 3382 }, 3383 }, { 3384 // TODO(wenovus): Support deviate delete for leaf-lists for config-false leafs once its semantics are clear. 3385 // https://github.com/mbj4668/pyang/issues/756 3386 desc: "error case - deviation delete on a leaf-list", 3387 inFiles: map[string]string{ 3388 "deviate": ` 3389 module deviate { 3390 prefix "d"; 3391 namespace "urn:d"; 3392 3393 leaf-list a { 3394 type string; 3395 default "fish"; 3396 } 3397 3398 deviation /a { 3399 deviate delete { 3400 default "fishsticks"; 3401 } 3402 } 3403 }`, 3404 }, 3405 wantProcessErrSubstring: "deviate delete on default statements unsupported for leaf-lists", 3406 }, { 3407 desc: "error case - deviation delete of default has different keyword value", 3408 inFiles: map[string]string{ 3409 "deviate": ` 3410 module deviate { 3411 prefix "d"; 3412 namespace "urn:d"; 3413 3414 leaf a { 3415 type string; 3416 default "fish"; 3417 } 3418 3419 deviation /a { 3420 deviate delete { 3421 default "fishsticks"; 3422 } 3423 } 3424 }`, 3425 }, 3426 wantProcessErrSubstring: "non-matching keyword", 3427 }, { 3428 desc: "error case - deviation delete where the default didn't exist", 3429 inFiles: map[string]string{ 3430 "deviate": ` 3431 module deviate { 3432 prefix "d"; 3433 namespace "urn:d"; 3434 3435 leaf a { 3436 type string; 3437 } 3438 3439 deviation /a { 3440 deviate delete { 3441 default "fishsticks"; 3442 } 3443 } 3444 }`, 3445 }, 3446 wantProcessErrSubstring: "default statement that doesn't exist", 3447 }, { 3448 desc: "error case - deviation delete of min-elements has different keyword value", 3449 inFiles: map[string]string{ 3450 "deviate": ` 3451 module deviate { 3452 prefix "d"; 3453 namespace "urn:d"; 3454 3455 leaf-list a { type string; } 3456 3457 deviation /a { 3458 deviate delete { 3459 min-elements 42; 3460 } 3461 } 3462 }`, 3463 }, 3464 wantProcessErrSubstring: "differs from deviation's min-element value", 3465 }, { 3466 desc: "error case - deviation delete of max-elements has different keyword value", 3467 inFiles: map[string]string{ 3468 "deviate": ` 3469 module deviate { 3470 prefix "d"; 3471 namespace "urn:d"; 3472 3473 leaf-list a { 3474 type string; 3475 max-elements 100; 3476 } 3477 3478 deviation /a { 3479 deviate delete { 3480 max-elements 42; 3481 } 3482 } 3483 }`, 3484 }, 3485 wantProcessErrSubstring: "differs from deviation's max-element value", 3486 }, { 3487 desc: "deviation using locally defined typedef", 3488 inFiles: map[string]string{ 3489 "deviate": ` 3490 module deviate { 3491 prefix "d"; 3492 namespace "urn:d"; 3493 3494 import source { prefix s; } 3495 3496 typedef rstr { 3497 type string { 3498 pattern "a.*"; 3499 } 3500 } 3501 3502 deviation /s:a { 3503 deviate replace { 3504 type rstr; 3505 } 3506 } 3507 } 3508 `, 3509 "source": ` 3510 module source { 3511 prefix "s"; 3512 namespace "urn:s"; 3513 3514 leaf a { type uint16; } 3515 } 3516 `, 3517 }, 3518 wants: map[string][]deviationTest{ 3519 "source": {{ 3520 path: "/a", 3521 entry: &Entry{ 3522 Type: &YangType{ 3523 Name: "rstr", 3524 Kind: Ystring, 3525 Pattern: []string{"a.*"}, 3526 }, 3527 }, 3528 }}, 3529 }, 3530 }, { 3531 desc: "complex deviation of multiple leaves", 3532 inFiles: map[string]string{ 3533 "foo": ` 3534 module foo { 3535 prefix "f"; 3536 namespace "urn:f"; 3537 3538 container a { leaf b { type string; } } 3539 3540 typedef abc { type boolean; } 3541 typedef abt { type uint32; } 3542 3543 deviation /a/b { 3544 // typedef is not valid here. 3545 //typedef abc { 3546 // type boolean; 3547 //} 3548 deviate replace { type abc; } 3549 } 3550 3551 deviation /a/b { 3552 // typedef is not valid here. 3553 //typedef abt { 3554 // type uint16; 3555 //} 3556 deviate replace { type abt; } 3557 } 3558 }`, 3559 }, 3560 wants: map[string][]deviationTest{ 3561 "foo": {{ 3562 path: "/a/b", 3563 entry: &Entry{ 3564 Type: &YangType{ 3565 Name: "abt", 3566 Kind: Yuint32, 3567 }, 3568 }, 3569 }}, 3570 }, 3571 }} 3572 3573 for _, tt := range tests { 3574 t.Run(tt.desc, func(t *testing.T) { 3575 ms := NewModules() 3576 ms.ParseOptions = tt.inParseOptions 3577 3578 for name, mod := range tt.inFiles { 3579 if err := ms.Parse(mod, name); err != nil { 3580 if diff := errdiff.Substring(err, tt.wantParseErrSubstring); diff != "" { 3581 t.Fatalf("error parsing module %s, %s", name, diff) 3582 } 3583 } 3584 } 3585 3586 errs := ms.Process() 3587 if len(errs) == 0 { 3588 // Add a nil error to compare against the wanted error string. 3589 errs = append(errs, nil) 3590 } 3591 var match bool 3592 for _, err := range errs { 3593 if diff := errdiff.Substring(err, tt.wantProcessErrSubstring); diff == "" { 3594 match = true 3595 break 3596 } 3597 } 3598 if !match { 3599 t.Fatalf("got errs: %v, want: %v", errs, tt.wantProcessErrSubstring) 3600 } 3601 3602 if tt.wantProcessErrSubstring == "" && len(tt.wants) == 0 { 3603 t.Fatalf("test case expects no error and no entry. Please change your test case to contain one of them.") 3604 } 3605 3606 for mod, tcs := range tt.wants { 3607 m, errs := ms.GetModule(mod) 3608 if errs != nil { 3609 t.Errorf("couldn't find module %s", mod) 3610 continue 3611 } 3612 3613 for idx, want := range tcs { 3614 got := m.Find(want.path) 3615 switch { 3616 case got == nil && want.entry != nil: 3617 t.Errorf("%d: expected entry %s does not exist", idx, want.path) 3618 continue 3619 case got != nil && want.entry == nil: 3620 t.Errorf("%d: unexpected entry %s exists, got: %v", idx, want.path, got) 3621 continue 3622 case want.entry == nil: 3623 continue 3624 } 3625 3626 if got.Config != want.entry.Config { 3627 t.Errorf("%d (%s): did not get expected config statement, got: %v, want: %v", idx, want.path, got.Config, want.entry.Config) 3628 } 3629 3630 if diff := cmp.Diff(got.Default, want.entry.Default, cmpopts.EquateEmpty()); diff != "" { 3631 t.Errorf("%d (%s): did not get expected default statement, (-got, +want): %s", idx, want.path, diff) 3632 } 3633 3634 if got.Mandatory != want.entry.Mandatory { 3635 t.Errorf("%d (%s): did not get expected mandatory statement, got: %v, want: %v", idx, want.path, got.Mandatory, want.entry.Mandatory) 3636 } 3637 3638 if want.entry.ListAttr != nil { 3639 if got.ListAttr == nil { 3640 t.Errorf("%d (%s): listattr was nil for an entry expected to be a list at %s", idx, want.path, want.path) 3641 continue 3642 } 3643 if want.entry.deviatePresence.hasMinElements { 3644 if gotMin, wantMin := got.ListAttr.MinElements, want.entry.ListAttr.MinElements; gotMin != wantMin { 3645 t.Errorf("%d (%s): min-elements, got: %v, want: %v", idx, want.path, gotMin, wantMin) 3646 } 3647 } 3648 if want.entry.deviatePresence.hasMaxElements { 3649 if gotMax, wantMax := got.ListAttr.MaxElements, want.entry.ListAttr.MaxElements; gotMax != wantMax { 3650 t.Errorf("%d (%s): max-elements, got: %v, want: %v", idx, want.path, gotMax, wantMax) 3651 } 3652 } 3653 } 3654 3655 if want.entry.Type != nil { 3656 if got.Type.Name != want.entry.Type.Name { 3657 t.Errorf("%d (%s): type name, got: %s, want: %s", idx, want.path, got.Type.Name, want.entry.Type.Name) 3658 } 3659 3660 if got.Type.Kind != want.entry.Type.Kind { 3661 t.Errorf("%d (%s): type kind, got: %s, want: %s", idx, want.path, got.Type.Kind, want.entry.Type.Kind) 3662 } 3663 } 3664 3665 if got.Units != want.entry.Units { 3666 t.Errorf("%d (%s): did not get expected units statement, got: %s, want: %s", idx, want.path, got.Units, want.entry.Units) 3667 } 3668 } 3669 } 3670 }) 3671 } 3672 } 3673 3674 func TestLeafEntry(t *testing.T) { 3675 tests := []struct { 3676 name string 3677 inModules map[string]string 3678 wantEntryPath string 3679 wantEntryCustomTest func(t *testing.T, e *Entry) 3680 wantErrSubstr string 3681 }{{ 3682 name: "direct decimal64 type", 3683 inModules: map[string]string{ 3684 "test.yang": ` 3685 module test { 3686 prefix "t"; 3687 namespace "urn:t"; 3688 3689 leaf "gain-adjustment" { 3690 type "decimal64" { 3691 fraction-digits "1"; 3692 range "-12.0..12.0"; 3693 } 3694 default "0.0"; 3695 } 3696 } 3697 `, 3698 }, 3699 wantEntryPath: "/test/gain-adjustment", 3700 wantEntryCustomTest: func(t *testing.T, e *Entry) { 3701 if got, want := e.Type.FractionDigits, 1; got != want { 3702 t.Errorf("got %d, want %d", got, want) 3703 } 3704 if got, want := e.Mandatory, TSUnset; got != want { 3705 t.Errorf("got %d, want %d", got, want) 3706 } 3707 if got, want := e.Type.Range, (YangRange{Rf(-120, 120, 1)}); !cmp.Equal(got, want) { 3708 t.Errorf("Range got: %v, want: %v", got, want) 3709 } 3710 }, 3711 }, { 3712 name: "typedef decimal64 type", 3713 inModules: map[string]string{ 3714 "test.yang": ` 3715 module test { 3716 prefix "t"; 3717 namespace "urn:t"; 3718 3719 typedef "optical-dB" { 3720 type "decimal64" { 3721 fraction-digits "1"; 3722 } 3723 } 3724 3725 leaf "gain-adjustment" { 3726 type "optical-dB" { 3727 range "-12.0..12.0"; 3728 } 3729 default "0.0"; 3730 } 3731 } 3732 `, 3733 }, 3734 wantEntryPath: "/test/gain-adjustment", 3735 wantEntryCustomTest: func(t *testing.T, e *Entry) { 3736 if got, want := e.Type.FractionDigits, 1; got != want { 3737 t.Errorf("got %d, want %d", got, want) 3738 } 3739 if diff := cmp.Diff(e.Type.Range, YangRange{Rf(-120, 120, 1)}); diff != "" { 3740 t.Errorf("Range (-got, +want):\n%s", diff) 3741 } 3742 }, 3743 }, { 3744 name: "typedef decimal64 type with overriding fraction-digits", 3745 inModules: map[string]string{ 3746 "test.yang": ` 3747 module test { 3748 prefix "t"; 3749 namespace "urn:t"; 3750 3751 typedef "optical-dB" { 3752 type "decimal64" { 3753 fraction-digits "1"; 3754 } 3755 } 3756 3757 leaf "gain-adjustment" { 3758 type "optical-dB" { 3759 fraction-digits "2"; 3760 range "-12.0..12.0"; 3761 } 3762 default "0.0"; 3763 } 3764 } 3765 `, 3766 }, 3767 wantErrSubstr: "overriding of fraction-digits not allowed", 3768 }, { 3769 name: "leaf mandatory true", 3770 inModules: map[string]string{ 3771 "test.yang": ` 3772 module test { 3773 prefix "t"; 3774 namespace "urn:t"; 3775 3776 leaf "mandatory" { 3777 type "string" { 3778 } 3779 mandatory true; 3780 } 3781 } 3782 `, 3783 }, 3784 wantEntryPath: "/test/mandatory", 3785 wantEntryCustomTest: func(t *testing.T, e *Entry) { 3786 if got, want := e.Mandatory, TSTrue; got != want { 3787 t.Errorf("got %d, want %d", got, want) 3788 } 3789 }, 3790 }, { 3791 name: "leaf mandatory false", 3792 inModules: map[string]string{ 3793 "test.yang": ` 3794 module test { 3795 prefix "t"; 3796 namespace "urn:t"; 3797 3798 leaf "mandatory" { 3799 type "string" { 3800 } 3801 mandatory false; 3802 } 3803 } 3804 `, 3805 }, 3806 wantEntryPath: "/test/mandatory", 3807 wantEntryCustomTest: func(t *testing.T, e *Entry) { 3808 if got, want := e.Mandatory, TSFalse; got != want { 3809 t.Errorf("got %d, want %d", got, want) 3810 } 3811 }, 3812 }, { 3813 name: "leaf description", 3814 inModules: map[string]string{ 3815 "test.yang": ` 3816 module test { 3817 prefix "t"; 3818 namespace "urn:t"; 3819 3820 leaf "mandatory" { 3821 type "string" { 3822 } 3823 description "I am a leaf"; 3824 } 3825 } 3826 `, 3827 }, 3828 wantEntryPath: "/test/mandatory", 3829 wantEntryCustomTest: func(t *testing.T, e *Entry) { 3830 if got, want := e.Description, "I am a leaf"; got != want { 3831 t.Errorf("got %q, want %q", got, want) 3832 } 3833 }, 3834 }} 3835 3836 for _, tt := range tests { 3837 t.Run(tt.name, func(t *testing.T) { 3838 ms := NewModules() 3839 var errs []error 3840 for n, m := range tt.inModules { 3841 if err := ms.Parse(m, n); err != nil { 3842 errs = append(errs, err) 3843 } 3844 } 3845 3846 if len(errs) > 0 { 3847 t.Fatalf("ms.Parse(), got unexpected error parsing input modules: %v", errs) 3848 } 3849 3850 if errs := ms.Process(); len(errs) > 0 { 3851 if len(errs) == 1 { 3852 if diff := errdiff.Substring(errs[0], tt.wantErrSubstr); diff != "" { 3853 t.Fatalf("did not get expected error, %s", diff) 3854 } 3855 return 3856 } 3857 t.Fatalf("ms.Process(), got too many errors processing entries: %v", errs) 3858 } 3859 3860 dir := map[string]*Entry{} 3861 for _, m := range ms.Modules { 3862 addTreeE(ToEntry(m), dir) 3863 } 3864 3865 e, ok := dir[tt.wantEntryPath] 3866 if !ok { 3867 t.Fatalf("could not find entry %s within the dir: %v", tt.wantEntryPath, dir) 3868 } 3869 tt.wantEntryCustomTest(t, e) 3870 }) 3871 } 3872 } 3873 3874 func TestLess(t *testing.T) { 3875 sErrors := sortedErrors{ 3876 {"testfile0", errors.New("test error0")}, 3877 {"testfile1", errors.New("test error1")}, 3878 {"testfile1:1", errors.New("test error2")}, 3879 {"testfile2:1", errors.New("test error3")}, 3880 {"testfile2:1:1", errors.New("test error4")}, 3881 {"testfile3:1:1:error5", errors.New("test error5")}, 3882 {"testfile3:1:2:error6", errors.New("test error6")}, 3883 {"testfile3:1:1:error7", errors.New("test error7")}, 3884 } 3885 3886 tests := []struct { 3887 desc string 3888 i int 3889 j int 3890 want bool 3891 }{{ 3892 desc: "compare two different strings without seperator ':'", 3893 i: 0, 3894 j: 1, 3895 want: true, 3896 }, { 3897 desc: "compare two different strings without seperator ':'", 3898 i: 1, 3899 j: 0, 3900 want: false, 3901 }, { 3902 desc: "compare one slice in a string with two slices in another string", 3903 i: 1, 3904 j: 2, 3905 want: true, 3906 }, { 3907 desc: "compare two different strings with two slices each", 3908 i: 2, 3909 j: 3, 3910 want: true, 3911 }, { 3912 desc: "compare two different strings with two slices each", 3913 i: 3, 3914 j: 2, 3915 want: false, 3916 }, { 3917 desc: "compare two slices in a string with three slices in another string", 3918 i: 3, 3919 j: 4, 3920 want: true, 3921 }, { 3922 desc: "compare three slices in a string with two slices in another string", 3923 i: 4, 3924 j: 3, 3925 want: false, 3926 }, { 3927 desc: "compare two different strings with four slices each", 3928 i: 5, 3929 j: 6, 3930 want: true, 3931 }, { 3932 desc: "compare two different strings with four slices each", 3933 i: 6, 3934 j: 5, 3935 want: false, 3936 }, { 3937 desc: "compare two identical strings without separator ':'", 3938 i: 1, 3939 j: 1, 3940 want: false, 3941 }, { 3942 desc: "compare two identical strings with two slices", 3943 i: 2, 3944 j: 2, 3945 want: false, 3946 }, { 3947 desc: "compare two identical strings with three slices", 3948 i: 4, 3949 j: 4, 3950 want: false, 3951 }, { 3952 desc: "compare two identical strings with four slices", 3953 i: 5, 3954 j: 5, 3955 want: false, 3956 }, { 3957 desc: "compare different strings with four slices", 3958 i: 7, 3959 j: 5, 3960 want: false, 3961 }, { 3962 desc: "compare different strings with four slices", 3963 i: 5, 3964 j: 7, 3965 want: true, 3966 }} 3967 var cmpSymbol byte 3968 for _, tt := range tests { 3969 want := sErrors.Less(tt.i, tt.j) 3970 if want != tt.want { 3971 if want { 3972 cmpSymbol = '<' 3973 } else { 3974 cmpSymbol = '>' 3975 } 3976 t.Errorf("%s: incorrect less comparison: \"%s\" %c \"%s\"", tt.desc, sErrors[tt.i].s, cmpSymbol, sErrors[tt.j].s) 3977 } 3978 } 3979 } 3980 3981 type customTestCases struct { 3982 wantEntryPath string 3983 wantEntryCustomTest func(t *testing.T, e *Entry) 3984 } 3985 3986 func TestOrderedBy(t *testing.T) { 3987 tests := []struct { 3988 name string 3989 inModules map[string]string 3990 testcases []customTestCases 3991 wantErrSubstr string 3992 }{{ 3993 name: "ordered-by user", 3994 inModules: map[string]string{ 3995 "test.yang": ` 3996 module test { 3997 prefix "t"; 3998 namespace "urn:t"; 3999 4000 list ordered-list { 4001 key "name"; 4002 ordered-by user; 4003 leaf name { 4004 type string; 4005 } 4006 } 4007 4008 list unordered-list { 4009 key "name"; 4010 ordered-by system; 4011 leaf name { 4012 type string; 4013 } 4014 } 4015 4016 list unordered-list2 { 4017 key "name"; 4018 leaf name { 4019 type string; 4020 } 4021 } 4022 4023 leaf-list ordered-leaflist { 4024 ordered-by user; 4025 type string; 4026 } 4027 4028 leaf-list unordered-leaflist { 4029 ordered-by system; 4030 type string; 4031 } 4032 4033 leaf-list unordered-leaflist2 { 4034 type string; 4035 } 4036 } 4037 `, 4038 }, 4039 testcases: []customTestCases{{ 4040 wantEntryPath: "/test/ordered-list", 4041 wantEntryCustomTest: func(t *testing.T, e *Entry) { 4042 if got, want := e.ListAttr.OrderedByUser, true; got != want { 4043 t.Errorf("got %v, want %v", got, want) 4044 } 4045 }, 4046 }, { 4047 wantEntryPath: "/test/unordered-list", 4048 wantEntryCustomTest: func(t *testing.T, e *Entry) { 4049 if got, want := e.ListAttr.OrderedByUser, false; got != want { 4050 t.Errorf("got %v, want %v", got, want) 4051 } 4052 }, 4053 }, { 4054 wantEntryPath: "/test/unordered-list2", 4055 wantEntryCustomTest: func(t *testing.T, e *Entry) { 4056 if got, want := e.ListAttr.OrderedByUser, false; got != want { 4057 t.Errorf("got %v, want %v", got, want) 4058 } 4059 }, 4060 }, { 4061 wantEntryPath: "/test/ordered-leaflist", 4062 wantEntryCustomTest: func(t *testing.T, e *Entry) { 4063 if got, want := e.ListAttr.OrderedByUser, true; got != want { 4064 t.Errorf("got %v, want %v", got, want) 4065 } 4066 }, 4067 }, { 4068 wantEntryPath: "/test/unordered-leaflist", 4069 wantEntryCustomTest: func(t *testing.T, e *Entry) { 4070 if got, want := e.ListAttr.OrderedByUser, false; got != want { 4071 t.Errorf("got %v, want %v", got, want) 4072 } 4073 }, 4074 }, { 4075 wantEntryPath: "/test/unordered-leaflist2", 4076 wantEntryCustomTest: func(t *testing.T, e *Entry) { 4077 if got, want := e.ListAttr.OrderedByUser, false; got != want { 4078 t.Errorf("got %v, want %v", got, want) 4079 } 4080 }, 4081 }}, 4082 }, { 4083 name: "ordered-by client: invalid argument", 4084 inModules: map[string]string{ 4085 "test.yang": ` 4086 module test { 4087 prefix "t"; 4088 namespace "urn:t"; 4089 4090 list ordered-list { 4091 key "name"; 4092 ordered-by client; 4093 leaf name { 4094 type string; 4095 } 4096 } 4097 } 4098 `, 4099 }, 4100 wantErrSubstr: "ordered-by has invalid argument", 4101 }} 4102 4103 for _, tt := range tests { 4104 t.Run(tt.name, func(t *testing.T) { 4105 ms := NewModules() 4106 var errs []error 4107 for n, m := range tt.inModules { 4108 if err := ms.Parse(m, n); err != nil { 4109 errs = append(errs, err) 4110 } 4111 } 4112 4113 if len(errs) > 0 { 4114 t.Fatalf("ms.Parse(), got unexpected error parsing input modules: %v", errs) 4115 } 4116 4117 if errs := ms.Process(); len(errs) > 0 { 4118 if len(errs) == 1 { 4119 if diff := errdiff.Substring(errs[0], tt.wantErrSubstr); diff != "" { 4120 t.Fatalf("did not get expected error, %s", diff) 4121 } 4122 return 4123 } 4124 t.Fatalf("ms.Process(), got too many errors processing entries: %v", errs) 4125 } 4126 4127 dir := map[string]*Entry{} 4128 for _, m := range ms.Modules { 4129 addTreeE(ToEntry(m), dir) 4130 } 4131 4132 for _, tc := range tt.testcases { 4133 e, ok := dir[tc.wantEntryPath] 4134 if !ok { 4135 t.Fatalf("could not find entry %s within the dir: %v", tc.wantEntryPath, dir) 4136 } 4137 tc.wantEntryCustomTest(t, e) 4138 } 4139 }) 4140 } 4141 }