github.com/openconfig/goyang@v1.4.5/pkg/yang/marshal_test.go (about) 1 // Copyright 2017 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 "encoding/json" 19 "fmt" 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 "github.com/kylelemons/godebug/pretty" 24 ) 25 26 func TestMarshalJSON(t *testing.T) { 27 tests := []struct { 28 name string 29 in *Entry 30 want string 31 wantErr bool 32 }{{ 33 name: "simple leaf entry", 34 in: &Entry{ 35 Name: "leaf", 36 Node: &Leaf{ 37 Name: "leaf", 38 }, 39 Description: "This is a fake leaf.", 40 Default: []string{"default-leaf-value"}, 41 Errors: []error{fmt.Errorf("error one")}, 42 Kind: LeafEntry, 43 Config: TSTrue, 44 Prefix: &Value{ 45 Name: "ModulePrefix", 46 Source: &Statement{ 47 Keyword: "prefix", 48 Argument: "ModulePrefix", 49 HasArgument: true, 50 }, 51 }, 52 Type: &YangType{ 53 Name: "string", 54 Kind: Ystring, 55 Default: "string-value", 56 }, 57 Annotation: map[string]interface{}{ 58 "fish": struct{ Side string }{"chips"}, 59 }, 60 }, 61 want: `{ 62 "Name": "leaf", 63 "Description": "This is a fake leaf.", 64 "Default": [ 65 "default-leaf-value" 66 ], 67 "Kind": 0, 68 "Config": 1, 69 "Prefix": { 70 "Name": "ModulePrefix", 71 "Source": { 72 "Keyword": "prefix", 73 "HasArgument": true, 74 "Argument": "ModulePrefix" 75 } 76 }, 77 "Type": { 78 "Name": "string", 79 "Kind": 18, 80 "Default": "string-value" 81 }, 82 "Annotation": { 83 "fish": { 84 "Side": "chips" 85 } 86 } 87 }`, 88 }, { 89 name: "simple container entry with parent", 90 in: &Entry{ 91 Name: "container", 92 Node: &Container{ 93 Name: "container", 94 }, 95 Kind: DirectoryEntry, 96 Config: TSFalse, 97 Prefix: &Value{ 98 Name: "ModulePrefix", 99 Source: &Statement{ 100 Keyword: "prefix", 101 Argument: "ModulePrefix", 102 HasArgument: true, 103 }, 104 }, 105 Dir: map[string]*Entry{ 106 "child": { 107 Name: "leaf", 108 Node: &Leaf{ 109 Name: "leaf", 110 }, 111 Kind: LeafEntry, 112 Config: TSUnset, 113 Prefix: &Value{ 114 Name: "ModulePrefix", 115 Source: &Statement{ 116 Keyword: "prefix", 117 Argument: "ModulePrefix", 118 HasArgument: true, 119 }, 120 }, 121 Type: &YangType{ 122 Name: "union", 123 Type: []*YangType{{ 124 Name: "string", 125 Pattern: []string{"^a.*$"}, 126 Kind: Ystring, 127 Length: YangRange{{ 128 Min: FromInt(10), 129 Max: FromInt(20), 130 }}, 131 }}, 132 }, 133 }, 134 }, 135 Augments: []*Entry{{ 136 Name: "augment", 137 Node: &Leaf{ 138 Name: "leaf", 139 }, 140 Kind: LeafEntry, 141 Config: TSFalse, 142 Prefix: &Value{ 143 Name: "ModulePrefix", 144 Source: &Statement{ 145 Keyword: "prefix", 146 Argument: "ModulePrefix", 147 HasArgument: true, 148 }, 149 }, 150 }}, 151 Augmented: []*Entry{{ 152 Name: "augmented", 153 Node: &Leaf{ 154 Name: "leaf", 155 }, 156 Kind: LeafEntry, 157 Config: TSTrue, 158 Prefix: &Value{ 159 Name: "ModulePrefix", 160 Source: &Statement{ 161 Keyword: "prefix", 162 Argument: "ModulePrefix", 163 HasArgument: true, 164 }, 165 }, 166 }}, 167 Uses: []*UsesStmt{{ 168 Uses: &Uses{ 169 Name: "grouping", 170 }, 171 Grouping: &Entry{ 172 Name: "grouping", 173 Node: &Grouping{ 174 Name: "grouping", 175 Leaf: []*Leaf{{ 176 Name: "groupingLeaf", 177 }}, 178 }, 179 Config: TSFalse, 180 Prefix: &Value{ 181 Name: "ModulePrefix", 182 Source: &Statement{ 183 Keyword: "prefix", 184 Argument: "ModulePrefix", 185 HasArgument: true, 186 }, 187 }, 188 }, 189 }}, 190 }, 191 want: `{ 192 "Name": "container", 193 "Kind": 1, 194 "Config": 2, 195 "Prefix": { 196 "Name": "ModulePrefix", 197 "Source": { 198 "Keyword": "prefix", 199 "HasArgument": true, 200 "Argument": "ModulePrefix" 201 } 202 }, 203 "Dir": { 204 "child": { 205 "Name": "leaf", 206 "Kind": 0, 207 "Config": 0, 208 "Prefix": { 209 "Name": "ModulePrefix", 210 "Source": { 211 "Keyword": "prefix", 212 "HasArgument": true, 213 "Argument": "ModulePrefix" 214 } 215 }, 216 "Type": { 217 "Name": "union", 218 "Kind": 0, 219 "Type": [ 220 { 221 "Name": "string", 222 "Kind": 18, 223 "Length": [ 224 { 225 "Min": { 226 "Value": 10, 227 "FractionDigits": 0, 228 "Negative": false 229 }, 230 "Max": { 231 "Value": 20, 232 "FractionDigits": 0, 233 "Negative": false 234 } 235 } 236 ], 237 "Pattern": [ 238 "^a.*$" 239 ] 240 } 241 ] 242 } 243 } 244 }, 245 "Augments": [ 246 { 247 "Name": "augment", 248 "Kind": 0, 249 "Config": 2, 250 "Prefix": { 251 "Name": "ModulePrefix", 252 "Source": { 253 "Keyword": "prefix", 254 "HasArgument": true, 255 "Argument": "ModulePrefix" 256 } 257 } 258 } 259 ], 260 "Augmented": [ 261 { 262 "Name": "augmented", 263 "Kind": 0, 264 "Config": 1, 265 "Prefix": { 266 "Name": "ModulePrefix", 267 "Source": { 268 "Keyword": "prefix", 269 "HasArgument": true, 270 "Argument": "ModulePrefix" 271 } 272 } 273 } 274 ], 275 "Uses": [ 276 { 277 "Uses": { 278 "Name": "grouping" 279 }, 280 "Grouping": { 281 "Name": "grouping", 282 "Kind": 0, 283 "Config": 2, 284 "Prefix": { 285 "Name": "ModulePrefix", 286 "Source": { 287 "Keyword": "prefix", 288 "HasArgument": true, 289 "Argument": "ModulePrefix" 290 } 291 } 292 } 293 } 294 ] 295 }`, 296 }, { 297 name: "Entry with list and leaflist", 298 in: &Entry{ 299 Name: "list", 300 Kind: DirectoryEntry, 301 Config: TSUnset, 302 Dir: map[string]*Entry{ 303 "leaf": { 304 Name: "string", 305 Kind: LeafEntry, 306 }, 307 "leaf-list": { 308 Name: "leaf-list", 309 ListAttr: &ListAttr{ 310 MaxElements: 18446744073709551615, 311 MinElements: 0, 312 }, 313 }, 314 }, 315 ListAttr: &ListAttr{ 316 MaxElements: 42, 317 MinElements: 48, 318 }, 319 Identities: []*Identity{{ 320 Name: "ID_ONE", 321 }}, 322 Exts: []*Statement{{ 323 Keyword: "some-extension:ext", 324 Argument: "ext-value", 325 HasArgument: true, 326 }}, 327 }, 328 want: `{ 329 "Name": "list", 330 "Kind": 1, 331 "Config": 0, 332 "Dir": { 333 "leaf": { 334 "Name": "string", 335 "Kind": 0, 336 "Config": 0 337 }, 338 "leaf-list": { 339 "Name": "leaf-list", 340 "Kind": 0, 341 "Config": 0, 342 "ListAttr": { 343 "MinElements": 0, 344 "MaxElements": 18446744073709551615, 345 "OrderedBy": null, 346 "OrderedByUser": false 347 } 348 } 349 }, 350 "Exts": [ 351 { 352 "Keyword": "some-extension:ext", 353 "HasArgument": true, 354 "Argument": "ext-value" 355 } 356 ], 357 "ListAttr": { 358 "MinElements": 48, 359 "MaxElements": 42, 360 "OrderedBy": null, 361 "OrderedByUser": false 362 }, 363 "Identities": [ 364 { 365 "Name": "ID_ONE" 366 } 367 ] 368 }`, 369 }} 370 371 for _, tt := range tests { 372 got, err := json.MarshalIndent(tt.in, "", " ") 373 if err != nil { 374 if !tt.wantErr { 375 t.Errorf("%s: json.MarshalIndent(%v, ...): got unexpected error: %v", tt.name, tt.in, err) 376 } 377 continue 378 } 379 380 if diff := pretty.Compare(string(got), tt.want); diff != "" { 381 t.Errorf("%s: jsonMarshalIndent(%v, ...): did not get expected JSON, diff(-got,+want):\n%s", tt.name, tt.in, diff) 382 } 383 } 384 } 385 386 func TestParseAndMarshal(t *testing.T) { 387 tests := []struct { 388 name string 389 in []inputModule 390 want map[string]string 391 }{{ 392 name: "simple single module", 393 in: []inputModule{{ 394 name: "test.yang", 395 content: `module test { 396 prefix "t"; 397 namespace "urn:t"; 398 399 typedef foobar { 400 type string { 401 length "10"; 402 } 403 } 404 405 identity "BASE"; 406 identity "DERIVED" { base "BASE"; } 407 408 container test { 409 list a { 410 key "k"; 411 min-elements 10; 412 max-elements "unbounded"; 413 leaf k { type string; } 414 415 leaf bar { 416 type foobar; 417 } 418 } 419 420 leaf d { 421 type decimal64 { 422 fraction-digits 8; 423 } 424 } 425 426 leaf-list zip { 427 type string; 428 } 429 430 leaf-list zip2 { 431 max-elements 1000; 432 type string; 433 } 434 435 leaf x { 436 type union { 437 type string; 438 type identityref { 439 base "BASE"; 440 } 441 } 442 } 443 } 444 }`, 445 }}, 446 want: map[string]string{ 447 "test": `{ 448 "Name": "test", 449 "Kind": 1, 450 "Config": 0, 451 "Prefix": { 452 "Name": "t", 453 "Source": { 454 "Keyword": "prefix", 455 "HasArgument": true, 456 "Argument": "t" 457 } 458 }, 459 "Dir": { 460 "test": { 461 "Name": "test", 462 "Kind": 1, 463 "Config": 0, 464 "Prefix": { 465 "Name": "t", 466 "Source": { 467 "Keyword": "prefix", 468 "HasArgument": true, 469 "Argument": "t" 470 } 471 }, 472 "Dir": { 473 "a": { 474 "Name": "a", 475 "Kind": 1, 476 "Config": 0, 477 "Prefix": { 478 "Name": "t", 479 "Source": { 480 "Keyword": "prefix", 481 "HasArgument": true, 482 "Argument": "t" 483 } 484 }, 485 "Dir": { 486 "bar": { 487 "Name": "bar", 488 "Kind": 0, 489 "Config": 0, 490 "Prefix": { 491 "Name": "t", 492 "Source": { 493 "Keyword": "prefix", 494 "HasArgument": true, 495 "Argument": "t" 496 } 497 }, 498 "Type": { 499 "Name": "foobar", 500 "Kind": 18, 501 "Length": [ 502 { 503 "Min": { 504 "Value": 10, 505 "FractionDigits": 0, 506 "Negative": false 507 }, 508 "Max": { 509 "Value": 10, 510 "FractionDigits": 0, 511 "Negative": false 512 } 513 } 514 ] 515 } 516 }, 517 "k": { 518 "Name": "k", 519 "Kind": 0, 520 "Config": 0, 521 "Prefix": { 522 "Name": "t", 523 "Source": { 524 "Keyword": "prefix", 525 "HasArgument": true, 526 "Argument": "t" 527 } 528 }, 529 "Type": { 530 "Name": "string", 531 "Kind": 18 532 } 533 } 534 }, 535 "Key": "k", 536 "ListAttr": { 537 "MinElements": 10, 538 "MaxElements": 18446744073709551615, 539 "OrderedBy": null, 540 "OrderedByUser": false 541 } 542 }, 543 "d": { 544 "Name": "d", 545 "Kind": 0, 546 "Config": 0, 547 "Prefix": { 548 "Name": "t", 549 "Source": { 550 "Keyword": "prefix", 551 "HasArgument": true, 552 "Argument": "t" 553 } 554 }, 555 "Type": { 556 "Name": "decimal64", 557 "Kind": 12, 558 "FractionDigits": 8, 559 "Range": [ 560 { 561 "Min": { 562 "Value": 9223372036854775808, 563 "FractionDigits": 8, 564 "Negative": true 565 }, 566 "Max": { 567 "Value": 9223372036854775807, 568 "FractionDigits": 8, 569 "Negative": false 570 } 571 } 572 ] 573 } 574 }, 575 "x": { 576 "Name": "x", 577 "Kind": 0, 578 "Config": 0, 579 "Prefix": { 580 "Name": "t", 581 "Source": { 582 "Keyword": "prefix", 583 "HasArgument": true, 584 "Argument": "t" 585 } 586 }, 587 "Type": { 588 "Name": "union", 589 "Kind": 19, 590 "Type": [ 591 { 592 "Name": "string", 593 "Kind": 18 594 }, 595 { 596 "Name": "identityref", 597 "Kind": 15, 598 "IdentityBase": { 599 "Name": "BASE", 600 "Values": [ 601 { 602 "Name": "DERIVED" 603 } 604 ] 605 } 606 } 607 ] 608 } 609 }, 610 "zip": { 611 "Name": "zip", 612 "Kind": 0, 613 "Config": 0, 614 "Prefix": { 615 "Name": "t", 616 "Source": { 617 "Keyword": "prefix", 618 "HasArgument": true, 619 "Argument": "t" 620 } 621 }, 622 "Type": { 623 "Name": "string", 624 "Kind": 18 625 }, 626 "ListAttr": { 627 "MinElements": 0, 628 "MaxElements": 18446744073709551615, 629 "OrderedBy": null, 630 "OrderedByUser": false 631 } 632 }, 633 "zip2": { 634 "Name": "zip2", 635 "Kind": 0, 636 "Config": 0, 637 "Prefix": { 638 "Name": "t", 639 "Source": { 640 "Keyword": "prefix", 641 "HasArgument": true, 642 "Argument": "t" 643 } 644 }, 645 "Type": { 646 "Name": "string", 647 "Kind": 18 648 }, 649 "ListAttr": { 650 "MinElements": 0, 651 "MaxElements": 1000, 652 "OrderedBy": null, 653 "OrderedByUser": false 654 } 655 } 656 } 657 } 658 }, 659 "Identities": [ 660 { 661 "Name": "BASE", 662 "Values": [ 663 { 664 "Name": "DERIVED" 665 } 666 ] 667 }, 668 { 669 "Name": "DERIVED" 670 } 671 ], 672 "extra-unstable": { 673 "namespace": [ 674 { 675 "Name": "urn:t", 676 "Source": { 677 "Keyword": "namespace", 678 "HasArgument": true, 679 "Argument": "urn:t" 680 } 681 } 682 ] 683 } 684 }`, 685 }, 686 }, { 687 name: "multiple modules with extension", 688 in: []inputModule{{ 689 name: "ext.yang", 690 content: `module ext { 691 prefix "e"; 692 namespace "urn:e"; 693 694 extension foobar { 695 argument "baz"; 696 } 697 }`, 698 }, { 699 name: "test.yang", 700 content: `module test { 701 prefix "t"; 702 namespace "urn:t"; 703 704 import ext { prefix ext; } 705 706 leaf t { 707 type string; 708 ext:foobar "marked"; 709 } 710 }`, 711 }}, 712 want: map[string]string{ 713 "test": `{ 714 "Name": "test", 715 "Kind": 1, 716 "Config": 0, 717 "Prefix": { 718 "Name": "t", 719 "Source": { 720 "Keyword": "prefix", 721 "HasArgument": true, 722 "Argument": "t" 723 } 724 }, 725 "Dir": { 726 "t": { 727 "Name": "t", 728 "Kind": 0, 729 "Config": 0, 730 "Prefix": { 731 "Name": "t", 732 "Source": { 733 "Keyword": "prefix", 734 "HasArgument": true, 735 "Argument": "t" 736 } 737 }, 738 "Type": { 739 "Name": "string", 740 "Kind": 18 741 }, 742 "Exts": [ 743 { 744 "Keyword": "ext:foobar", 745 "HasArgument": true, 746 "Argument": "marked" 747 } 748 ] 749 } 750 }, 751 "extra-unstable": { 752 "namespace": [ 753 { 754 "Name": "urn:t", 755 "Source": { 756 "Keyword": "namespace", 757 "HasArgument": true, 758 "Argument": "urn:t" 759 } 760 } 761 ] 762 } 763 }`, 764 "ext": `{ 765 "Name": "ext", 766 "Kind": 1, 767 "Config": 0, 768 "Prefix": { 769 "Name": "e", 770 "Source": { 771 "Keyword": "prefix", 772 "HasArgument": true, 773 "Argument": "e" 774 } 775 }, 776 "extra-unstable": { 777 "extension": [ 778 { 779 "Name": "foobar", 780 "Argument": { 781 "Name": "baz" 782 } 783 } 784 ], 785 "namespace": [ 786 { 787 "Name": "urn:e", 788 "Source": { 789 "Keyword": "namespace", 790 "HasArgument": true, 791 "Argument": "urn:e" 792 } 793 } 794 ] 795 } 796 }`, 797 }, 798 }} 799 800 for _, tt := range tests { 801 ms := NewModules() 802 803 for _, mod := range tt.in { 804 if err := ms.Parse(mod.content, mod.name); err != nil { 805 t.Errorf("%s: ms.Parse(..., %v): parsing error with module: %v", tt.name, mod.name, err) 806 continue 807 } 808 809 if errs := ms.Process(); len(errs) != 0 { 810 t.Errorf("%s: ms.Process(): could not parse modules: %v", tt.name, errs) 811 continue 812 } 813 814 entries := make(map[string]*Entry) 815 for _, m := range ms.Modules { 816 if _, ok := entries[m.Name]; !ok { 817 entries[m.Name] = ToEntry(m) 818 819 got, err := json.MarshalIndent(entries[m.Name], "", " ") 820 if err != nil { 821 t.Errorf("%s: json.MarshalIndent(...): got unexpected error: %v", tt.name, err) 822 continue 823 } 824 825 if diff := cmp.Diff(string(got), tt.want[m.Name]); diff != "" { 826 t.Errorf("%s: json.MarshalIndent(...): did not get expected JSON, diff(-got,+want):\n%s", tt.name, diff) 827 } 828 } 829 } 830 } 831 } 832 }