github.com/openconfig/goyang@v1.4.5/pkg/yang/node_test.go (about)

     1  // Copyright 2019 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  	"errors"
    19  	"fmt"
    20  	"testing"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/openconfig/gnmi/errdiff"
    24  )
    25  
    26  func TestNodePath(t *testing.T) {
    27  	tests := []struct {
    28  		desc string
    29  		in   Node
    30  		want string
    31  	}{{
    32  		desc: "basic",
    33  		in: &Leaf{
    34  			Name: "bar",
    35  			Parent: &Container{
    36  				Name: "c",
    37  				Parent: &List{
    38  					Name: "b",
    39  					Parent: &Module{
    40  						Name: "foo",
    41  					},
    42  				},
    43  			},
    44  		},
    45  		want: "/foo/b/c/bar",
    46  	}, {
    47  		desc: "nil input node",
    48  		in:   nil,
    49  		want: "",
    50  	}, {
    51  		desc: "single node",
    52  		in: &Module{
    53  			Name: "foo",
    54  		},
    55  		want: "/foo",
    56  	}}
    57  
    58  	for _, tt := range tests {
    59  		t.Run(tt.desc, func(t *testing.T) {
    60  			if diff := cmp.Diff(NodePath(tt.in), tt.want); diff != "" {
    61  				t.Errorf("(-got, +want):\n%s", diff)
    62  			}
    63  		})
    64  	}
    65  }
    66  
    67  // TestNode provides a framework for processing tests that can check particular
    68  // nodes being added to the grammar. It can be used to ensure that particular
    69  // statement combinations are supported, especially where they are opaque to
    70  // the YANG library.
    71  func TestNode(t *testing.T) {
    72  	tests := []struct {
    73  		desc          string
    74  		inFn          func(*Modules) (Node, error)
    75  		inModules     map[string]string
    76  		wantNode      func(Node) error
    77  		wantErrSubstr string
    78  	}{{
    79  		desc: "import reference statement",
    80  		inFn: func(ms *Modules) (Node, error) {
    81  
    82  			const module = "test"
    83  			m, ok := ms.Modules[module]
    84  			if !ok {
    85  				return nil, fmt.Errorf("can't find module %q", module)
    86  			}
    87  
    88  			if len(m.Import) == 0 {
    89  				return nil, fmt.Errorf("node %v is missing imports", m)
    90  			}
    91  
    92  			return m.Import[0], nil
    93  		},
    94  		inModules: map[string]string{
    95  			"test": `
    96  				module test {
    97  					prefix "t";
    98  					namespace "urn:t";
    99  
   100  					import foo {
   101  						prefix "f";
   102  						reference "bar";
   103  					}
   104  				}
   105  			`,
   106  			"foo": `
   107  				module foo {
   108  					prefix "f";
   109  					namespace "urn:f";
   110  				}
   111  			`,
   112  		},
   113  		wantNode: func(n Node) error {
   114  			is, ok := n.(*Import)
   115  			if !ok {
   116  				return fmt.Errorf("got node: %v, want type: import", n)
   117  			}
   118  
   119  			switch {
   120  			case is.Reference == nil:
   121  				return errors.New("did not get expected reference, got: nil, want: *yang.Statement")
   122  			case is.Reference.Statement().Argument != "bar":
   123  				return fmt.Errorf("did not get expected reference, got: %v, want: 'bar'", is.Reference.Statement())
   124  			}
   125  
   126  			return nil
   127  		},
   128  	}, {
   129  		desc: "get submodule from prefix in submodule",
   130  		inFn: func(ms *Modules) (Node, error) {
   131  
   132  			m, ok := ms.SubModules["foo"]
   133  			if !ok {
   134  				return nil, fmt.Errorf("can't find submodule in %v", ms)
   135  			}
   136  
   137  			if m.BelongsTo == nil {
   138  				return nil, fmt.Errorf("node %v is missing belongs-to", m)
   139  			}
   140  
   141  			return m.BelongsTo, nil
   142  		},
   143  		inModules: map[string]string{
   144  			"test": `
   145  				module test {
   146  					prefix "t";
   147  					namespace "urn:t";
   148  
   149  					include foo {
   150  						revision-date 2008-01-01;
   151  					}
   152  				}
   153  			`,
   154  			"foo": `
   155  				submodule foo {
   156  					belongs-to test {
   157  					  prefix "t";
   158  					}
   159  				}
   160  			`,
   161  		},
   162  		wantNode: func(n Node) error {
   163  			is, ok := n.(*BelongsTo)
   164  			if !ok {
   165  				return fmt.Errorf("got node: %v, want type: belongs-to", n)
   166  			}
   167  
   168  			switch {
   169  			case is.Prefix == nil:
   170  				return errors.New("did not get expected reference, got: nil, want: *yang.Statement")
   171  			case is.Prefix.Statement().Argument != "t":
   172  				return fmt.Errorf("did not get expected reference, got: %v, want: 't'", is.Prefix.Statement())
   173  			}
   174  
   175  			m := FindModuleByPrefix(is, is.Prefix.Statement().Argument)
   176  			if m == nil {
   177  				return fmt.Errorf("can't find module from submodule's belongs-to prefix value")
   178  			}
   179  			if want := "foo"; m.Name != want {
   180  				return fmt.Errorf("module from submodule's belongs-to prefix value doesn't match, got %q, want %q", m.Name, want)
   181  			}
   182  
   183  			return nil
   184  		},
   185  	}, {
   186  		desc: "import statement from submodule",
   187  		inFn: func(ms *Modules) (Node, error) {
   188  
   189  			m, ok := ms.SubModules["foo"]
   190  			if !ok {
   191  				return nil, fmt.Errorf("can't find submodule in %v", ms)
   192  			}
   193  
   194  			if len(m.Import) == 0 {
   195  				return nil, fmt.Errorf("node %v is missing import statement", m)
   196  			}
   197  
   198  			return m.Import[0], nil
   199  		},
   200  		inModules: map[string]string{
   201  			"test": `
   202  				module test {
   203  					prefix "t";
   204  					namespace "urn:t";
   205  
   206  					include foo {
   207  						revision-date 2008-01-01;
   208  					}
   209  
   210  					typedef t {
   211  						type string;
   212  					}
   213  				}
   214  			`,
   215  			"foo": `
   216  				submodule foo {
   217  					belongs-to test {
   218  					  prefix "t";
   219  					}
   220  
   221  					import test2 {
   222  						prefix "t2";
   223  						description "test2 module";
   224  					}
   225  				}
   226  			`,
   227  			"test2": `
   228  				module test2 {
   229  					prefix "t2";
   230  					namespace "urn:t2";
   231  				}
   232  			`,
   233  		},
   234  		wantNode: func(n Node) error {
   235  			is, ok := n.(*Import)
   236  			if !ok {
   237  				return fmt.Errorf("got node: %v, want type: belongs-to", n)
   238  			}
   239  
   240  			switch {
   241  			case is.Prefix == nil:
   242  				return errors.New("did not get expected reference, got: nil, want: *yang.Statement")
   243  			case is.Prefix.Statement().Argument != "t2":
   244  				return fmt.Errorf("did not get expected reference, got: %v, want: 't'", is.Prefix.Statement())
   245  			}
   246  
   247  			m := FindModuleByPrefix(is, is.Prefix.Statement().Argument)
   248  			if m == nil {
   249  				return fmt.Errorf("can't find module from submodule's import prefix value")
   250  			}
   251  			if want := "test2"; m.Name != want {
   252  				return fmt.Errorf("module from submodule's import prefix value doesn't match, got %q, want %q", m.Name, want)
   253  			}
   254  
   255  			return nil
   256  		},
   257  	}, {
   258  		desc: "import description statement",
   259  		inFn: func(ms *Modules) (Node, error) {
   260  
   261  			const module = "test"
   262  			m, ok := ms.Modules[module]
   263  			if !ok {
   264  				return nil, fmt.Errorf("can't find module %q", module)
   265  			}
   266  
   267  			if len(m.Import) == 0 {
   268  				return nil, fmt.Errorf("node %v is missing imports", m)
   269  			}
   270  
   271  			return m.Import[0], nil
   272  		},
   273  		inModules: map[string]string{
   274  			"test": `
   275  				module test {
   276  					prefix "t";
   277  					namespace "urn:t";
   278  
   279  					import foo {
   280  						prefix "f";
   281  						description "foo module";
   282  					}
   283  				}
   284  			`,
   285  			"foo": `
   286  				module foo {
   287  					prefix "f";
   288  					namespace "urn:f";
   289  				}
   290  			`,
   291  		},
   292  		wantNode: func(n Node) error {
   293  			is, ok := n.(*Import)
   294  			if !ok {
   295  				return fmt.Errorf("got node: %v, want type: import", n)
   296  			}
   297  
   298  			switch {
   299  			case is.Description == nil:
   300  				return errors.New("did not get expected reference, got: nil, want: *yang.Statement")
   301  			case is.Description.Statement().Argument != "foo module":
   302  				return fmt.Errorf("did not get expected reference, got: '%v', want: 'foo module'", is.Description.Statement().Argument)
   303  			}
   304  
   305  			return nil
   306  		},
   307  	}, {
   308  		desc: "Test matchingExtensions",
   309  		inFn: func(ms *Modules) (Node, error) {
   310  
   311  			module := "test"
   312  			m, ok := ms.Modules[module]
   313  			if !ok {
   314  				return nil, fmt.Errorf("can't find module %q", module)
   315  			}
   316  
   317  			if len(m.Leaf) == 0 {
   318  				return nil, fmt.Errorf("node %v is missing imports", m)
   319  			}
   320  
   321  			module = "foo"
   322  			if _, ok := ms.Modules[module]; !ok {
   323  				return nil, fmt.Errorf("can't find module %q", module)
   324  			}
   325  
   326  			return m.Leaf[0].Type, nil
   327  		},
   328  		inModules: map[string]string{
   329  			"test": `
   330  				module test {
   331  					prefix "t";
   332  					namespace "urn:t";
   333  
   334  					import foo {
   335  						prefix "f";
   336  						description "foo module";
   337  					}
   338  
   339  					import foo2 {
   340  						prefix "f2";
   341  						description "foo2 module";
   342  					}
   343  
   344  					leaf test-leaf {
   345  						type string {
   346  							pattern 'alpha';
   347  							// Test different modules and different ext names.
   348  							f:bar 'boo';
   349  							f2:bar 'boo2';
   350  
   351  							f:bar 'coo';
   352  							f2:bar 'coo2';
   353  
   354  							f:far 'doo';
   355  							f2:far 'doo2';
   356  
   357  							f:bar 'foo';
   358  							f2:bar 'foo2';
   359  
   360  							f:far 'goo';
   361  							f2:far 'goo2';
   362  						}
   363  					}
   364  				}
   365  			`,
   366  			"foo": `
   367  				module foo {
   368  					prefix "f";
   369  					namespace "urn:f";
   370  
   371  					extension bar {
   372  						argument "baz";
   373  					}
   374  
   375  					extension far {
   376  						argument "baz";
   377  					}
   378  				}
   379  			`,
   380  			"foo2": `
   381  				module foo2 {
   382  					prefix "f2";
   383  					namespace "urn:f2";
   384  
   385  					extension bar {
   386  						argument "baz";
   387  					}
   388  
   389  					extension far {
   390  						argument "baz";
   391  					}
   392  				}
   393  			`,
   394  		},
   395  		wantNode: func(n Node) error {
   396  			n, ok := n.(*Type)
   397  			if !ok {
   398  				return fmt.Errorf("got node: %v, want type: Leaf", n)
   399  			}
   400  
   401  			var bars []string
   402  			matches, err := matchingExtensions(n, n.Exts(), "foo", "bar")
   403  			if err != nil {
   404  				return err
   405  			}
   406  			for _, ext := range matches {
   407  				bars = append(bars, ext.Argument)
   408  			}
   409  
   410  			if diff := cmp.Diff(bars, []string{"boo", "coo", "foo"}); diff != "" {
   411  				return fmt.Errorf("matchingExtensions (-got, +want):\n%s", diff)
   412  			}
   413  
   414  			return nil
   415  		},
   416  	}, {
   417  		desc: "Test matchingExtensions when module is not found",
   418  		inFn: func(ms *Modules) (Node, error) {
   419  
   420  			module := "test"
   421  			m, ok := ms.Modules[module]
   422  			if !ok {
   423  				return nil, fmt.Errorf("can't find module %q", module)
   424  			}
   425  
   426  			if len(m.Leaf) == 0 {
   427  				return nil, fmt.Errorf("node %v is missing imports", m)
   428  			}
   429  
   430  			module = "foo"
   431  			if _, ok := ms.Modules[module]; !ok {
   432  				return nil, fmt.Errorf("can't find module %q", module)
   433  			}
   434  
   435  			return m.Leaf[0].Type, nil
   436  		},
   437  		inModules: map[string]string{
   438  			"test": `
   439  				module test {
   440  					prefix "t";
   441  					namespace "urn:t";
   442  
   443  					import foo {
   444  						prefix "f";
   445  						description "foo module";
   446  					}
   447  
   448  					leaf test-leaf {
   449  						type string {
   450  							pattern 'alpha';
   451  							not-found:bar 'foo';
   452  						}
   453  					}
   454  				}
   455  			`,
   456  			"foo": `
   457  				module foo {
   458  					prefix "f";
   459  					namespace "urn:f";
   460  
   461  					extension bar {
   462  						argument "baz";
   463  					}
   464  
   465  					extension far {
   466  						argument "baz";
   467  					}
   468  				}
   469  			`,
   470  		},
   471  		wantNode: func(n Node) error {
   472  			n, ok := n.(*Type)
   473  			if !ok {
   474  				return fmt.Errorf("got node: %v, want type: Leaf", n)
   475  			}
   476  
   477  			var bars []string
   478  			matches, err := matchingExtensions(n, n.Exts(), "foo", "bar")
   479  			if err != nil {
   480  				return err
   481  			}
   482  			for _, ext := range matches {
   483  				bars = append(bars, ext.Argument)
   484  			}
   485  
   486  			if diff := cmp.Diff(bars, []string{"boo", "coo", "foo"}); diff != "" {
   487  				return fmt.Errorf("matchingExtensions (-got, +want):\n%s", diff)
   488  			}
   489  
   490  			return nil
   491  		},
   492  		wantErrSubstr: `module prefix "not-found" not found`,
   493  	}}
   494  
   495  	for _, tt := range tests {
   496  		t.Run(tt.desc, func(t *testing.T) {
   497  			ms := NewModules()
   498  
   499  			for n, m := range tt.inModules {
   500  				if err := ms.Parse(m, n); err != nil {
   501  					t.Errorf("error parsing module %s, got: %v, want: nil", n, err)
   502  				}
   503  			}
   504  
   505  			errs := ms.Process()
   506  			var err error
   507  			if len(errs) > 1 {
   508  				t.Fatalf("Got more than 1 error: %v", errs)
   509  			} else if len(errs) == 1 {
   510  				err = errs[0]
   511  			}
   512  			if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" {
   513  				t.Errorf("Did not get expected error: %s", diff)
   514  			}
   515  			if err != nil {
   516  				return
   517  			}
   518  
   519  			node, err := tt.inFn(ms)
   520  			if err != nil {
   521  				t.Fatalf("cannot run in function, %v", err)
   522  			}
   523  
   524  			if err := tt.wantNode(node); err != nil {
   525  				t.Fatalf("failed check function, %v", err)
   526  			}
   527  		})
   528  	}
   529  }
   530  
   531  func TestModulesFindByPrefix(t *testing.T) {
   532  	// Some examples of where prefixes might be used are in the following
   533  	// YANG statements: extension, uses, augment, deviation, type, leafref.
   534  	// Not all are put into the test here, since the logic is the same for
   535  	// each.
   536  	modules := map[string]string{
   537  		"foo": `module foo { prefix "foo"; namespace "urn:foo"; include bar; leaf leafref { type leafref { path "../foo:leaf"; } } uses foo:lg; }`,
   538  		"bar": `submodule bar { belongs-to foo { prefix "bar"; } container c { uses bar:lg; } grouping lg { leaf leaf { type string; } } }`,
   539  		"baz": `module baz { prefix "foo"; namespace "urn:foo"; import foo { prefix f; } extension e; uses f:lg; foo:e; }`,
   540  	}
   541  
   542  	ms := NewModules()
   543  	for name, modtext := range modules {
   544  		if err := ms.Parse(modtext, name+".yang"); err != nil {
   545  			t.Fatalf("error parsing module %q: %v", name, err)
   546  		}
   547  	}
   548  	if errs := ms.Process(); errs != nil {
   549  		for _, err := range errs {
   550  			t.Errorf("error: %v", err)
   551  		}
   552  		t.Fatalf("fatal error(s) calling Process()")
   553  	}
   554  
   555  	for _, tt := range []struct {
   556  		desc   string
   557  		node   Node
   558  		prefix string
   559  		want   *Module
   560  	}{
   561  		{
   562  			desc:   "nil node",
   563  			node:   nil,
   564  			prefix: "does-not-exist",
   565  			want:   nil,
   566  		},
   567  		{
   568  			desc:   "module foo",
   569  			node:   ms.Modules["foo"],
   570  			prefix: "foo",
   571  			want:   ms.Modules["foo"],
   572  		},
   573  		{
   574  			desc:   "submodule bar",
   575  			node:   ms.SubModules["bar"],
   576  			prefix: "bar",
   577  			want:   ms.SubModules["bar"],
   578  		},
   579  		{
   580  			desc:   "module baz",
   581  			node:   ms.Modules["baz"],
   582  			prefix: "foo",
   583  			want:   ms.Modules["baz"],
   584  		},
   585  		{
   586  			desc:   "foo leafref",
   587  			node:   ms.Modules["foo"].Leaf[0].Type,
   588  			prefix: "foo",
   589  			want:   ms.Modules["foo"],
   590  		},
   591  		{
   592  			desc:   "foo uses",
   593  			node:   ms.Modules["foo"].Uses[0],
   594  			prefix: "foo",
   595  			want:   ms.Modules["foo"],
   596  		},
   597  		{
   598  			desc:   "bar uses",
   599  			node:   ms.SubModules["bar"].Container[0].Uses[0],
   600  			prefix: "bar",
   601  			want:   ms.SubModules["bar"],
   602  		},
   603  		{
   604  			desc:   "baz uses",
   605  			node:   ms.Modules["baz"].Uses[0],
   606  			prefix: "f",
   607  			want:   ms.Modules["foo"],
   608  		},
   609  		{
   610  			desc:   "baz extension",
   611  			node:   ms.Modules["baz"],
   612  			prefix: "foo",
   613  			want:   ms.Modules["baz"],
   614  		},
   615  	} {
   616  		t.Run(tt.desc, func(t *testing.T) {
   617  			if got := FindModuleByPrefix(tt.node, tt.prefix); got != tt.want {
   618  				t.Errorf("got: %v, want: %v", got, tt.want)
   619  			}
   620  		})
   621  	}
   622  }