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  }