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  }