github.com/openconfig/goyang@v1.4.5/pkg/yang/types_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  	"fmt"
    19  	"testing"
    20  
    21  	"github.com/google/go-cmp/cmp"
    22  	"github.com/google/go-cmp/cmp/cmpopts"
    23  	"github.com/openconfig/gnmi/errdiff"
    24  )
    25  
    26  func TestTypeResolve(t *testing.T) {
    27  	tests := []struct {
    28  		desc string
    29  		in   *Type
    30  		err  string
    31  		out  *YangType
    32  	}{{
    33  		desc: "basic int64",
    34  		in: &Type{
    35  			Name: "int64",
    36  		},
    37  		out: &YangType{
    38  			Name:  "int64",
    39  			Kind:  Yint64,
    40  			Range: Int64Range,
    41  		},
    42  	}, {
    43  		desc: "basic int64 with a range",
    44  		in: &Type{
    45  			Name:  "int64",
    46  			Range: &Range{Name: "-42..42"},
    47  		},
    48  		out: &YangType{
    49  			Name:  "int64",
    50  			Kind:  Yint64,
    51  			Range: YangRange{{Min: FromInt(-42), Max: FromInt(42)}},
    52  		},
    53  	}, {
    54  		desc: "basic uint64 with an invalid range",
    55  		in: &Type{
    56  			Name:  "uint64",
    57  			Range: &Range{Name: "-42..42"},
    58  		},
    59  		err: "unknown: bad range: -42..42 not within 0..18446744073709551615",
    60  	}, {
    61  		desc: "basic uint64 with an unparseable range",
    62  		in: &Type{
    63  			Name:  "uint64",
    64  			Range: &Range{Name: "-42..forty-two"},
    65  		},
    66  		err: `unknown: bad range: strconv.ParseUint: parsing "forty-two": invalid syntax`,
    67  	}, {
    68  		desc: "basic string with a length",
    69  		in: &Type{
    70  			Name:   "string",
    71  			Length: &Length{Name: "24..42"},
    72  		},
    73  		out: &YangType{
    74  			Name:   "string",
    75  			Kind:   Ystring,
    76  			Length: YangRange{{Min: FromInt(24), Max: FromInt(42)}},
    77  		},
    78  	}, {
    79  		desc: "basic string with an invalid range",
    80  		in: &Type{
    81  			Name:   "string",
    82  			Length: &Length{Name: "-42..42"},
    83  		},
    84  		err: `unknown: bad length: -42..42 not within 0..18446744073709551615`,
    85  	}, {
    86  		desc: "basic binary with a length",
    87  		in: &Type{
    88  			Name:   "binary",
    89  			Length: &Length{Name: "24..42"},
    90  		},
    91  		out: &YangType{
    92  			Name:   "binary",
    93  			Kind:   Ybinary,
    94  			Length: YangRange{{Min: FromInt(24), Max: FromInt(42)}},
    95  		},
    96  	}, {
    97  		desc: "basic binary with an unparseable range",
    98  		in: &Type{
    99  			Name:   "binary",
   100  			Length: &Length{Name: "42..forty-two"},
   101  		},
   102  		err: `unknown: bad length: strconv.ParseUint: parsing "forty-two": invalid syntax`,
   103  	}, {
   104  		desc: "invalid fraction-digits argument for boolean value",
   105  		in: &Type{
   106  			Name:           "boolean",
   107  			FractionDigits: &Value{Name: "42"},
   108  		},
   109  		err: "unknown: fraction-digits only allowed for decimal64 values",
   110  	}, {
   111  		desc: "required field fraction-digits not supplied for decimal64",
   112  		in: &Type{
   113  			Name: "decimal64",
   114  		},
   115  		err: "unknown: value is required in the range of [1..18]",
   116  	}, {
   117  		desc: "invalid identityref that doesn't have a base identity name",
   118  		in: &Type{
   119  			Name: "identityref",
   120  		},
   121  		err: "unknown: an identityref must specify a base",
   122  	}, {
   123  		desc: "invalid decimal64 having an invalid fraction-digits value",
   124  		in: &Type{
   125  			Name:           "decimal64",
   126  			FractionDigits: &Value{Name: "42"},
   127  		},
   128  		err: "unknown: value 42 out of range [1..18]",
   129  	}, {
   130  		desc: "decimal64",
   131  		in: &Type{
   132  			Name:           "decimal64",
   133  			FractionDigits: &Value{Name: "7"},
   134  		},
   135  		out: &YangType{
   136  			Name:           "decimal64",
   137  			Kind:           Ydecimal64,
   138  			FractionDigits: 7,
   139  			Range:          YangRange{Rf(MinInt64, MaxInt64, 7)},
   140  		},
   141  	}, {
   142  		desc: "instance-identifier with unspecified require-instance value (default true)",
   143  		in: &Type{
   144  			Name:            "instance-identifier",
   145  			RequireInstance: nil,
   146  		},
   147  		out: &YangType{
   148  			Name: "instance-identifier",
   149  			Kind: YinstanceIdentifier,
   150  			// https://tools.ietf.org/html/rfc7950#section-9.9.3
   151  			// require-instance defaults to true.
   152  			OptionalInstance: false,
   153  		},
   154  	}, {
   155  		desc: "instance-identifier with true require-instance value",
   156  		in: &Type{
   157  			Name:            "instance-identifier",
   158  			RequireInstance: &Value{Name: "true"},
   159  		},
   160  		out: &YangType{
   161  			Name:             "instance-identifier",
   162  			Kind:             YinstanceIdentifier,
   163  			OptionalInstance: false,
   164  		},
   165  	}, {
   166  		desc: "instance-identifier with false require-instance value",
   167  		in: &Type{
   168  			Name:            "instance-identifier",
   169  			RequireInstance: &Value{Name: "false"},
   170  		},
   171  		out: &YangType{
   172  			Name:             "instance-identifier",
   173  			Kind:             YinstanceIdentifier,
   174  			OptionalInstance: true,
   175  		},
   176  	}, {
   177  		desc: "instance-identifier with invalid require-instance value",
   178  		in: &Type{
   179  			Name:            "instance-identifier",
   180  			RequireInstance: &Value{Name: "foo"},
   181  		},
   182  		err: "invalid boolean: foo",
   183  	}, {
   184  		desc: "enum with unspecified values",
   185  		in: &Type{
   186  			Name: "enumeration",
   187  			Enum: []*Enum{
   188  				{Name: "MERCURY"},
   189  				{Name: "VENUS"},
   190  				{Name: "EARTH"},
   191  			},
   192  		},
   193  		out: &YangType{
   194  			Name: "enumeration",
   195  			Kind: Yenum,
   196  			Enum: &EnumType{
   197  				last:   2,
   198  				min:    MinEnum,
   199  				max:    MaxEnum,
   200  				unique: true,
   201  				ToString: map[int64]string{
   202  					0: "MERCURY",
   203  					1: "VENUS",
   204  					2: "EARTH",
   205  				},
   206  				ToInt: map[string]int64{
   207  					"MERCURY": 0,
   208  					"VENUS":   1,
   209  					"EARTH":   2,
   210  				},
   211  			},
   212  		},
   213  	}, {
   214  		desc: "enum with specified values",
   215  		in: &Type{
   216  			Name: "enumeration",
   217  			Enum: []*Enum{
   218  				{Name: "MERCURY", Value: &Value{Name: "-1"}},
   219  				{Name: "VENUS", Value: &Value{Name: "10"}},
   220  				{Name: "EARTH", Value: &Value{Name: "30"}},
   221  			},
   222  		},
   223  		out: &YangType{
   224  			Name: "enumeration",
   225  			Kind: Yenum,
   226  			Enum: &EnumType{
   227  				last:   30,
   228  				min:    MinEnum,
   229  				max:    MaxEnum,
   230  				unique: true,
   231  				ToString: map[int64]string{
   232  					-1: "MERCURY",
   233  					10: "VENUS",
   234  					30: "EARTH",
   235  				},
   236  				ToInt: map[string]int64{
   237  					"MERCURY": -1,
   238  					"VENUS":   10,
   239  					"EARTH":   30,
   240  				},
   241  			},
   242  		},
   243  	}, {
   244  		desc: "enum with some values specified",
   245  		in: &Type{
   246  			Name: "enumeration",
   247  			Enum: []*Enum{
   248  				{Name: "MERCURY", Value: &Value{Name: "-1"}},
   249  				{Name: "VENUS", Value: &Value{Name: "10"}},
   250  				{Name: "EARTH"},
   251  			},
   252  		},
   253  		out: &YangType{
   254  			Name: "enumeration",
   255  			Kind: Yenum,
   256  			Enum: &EnumType{
   257  				last:   11,
   258  				min:    MinEnum,
   259  				max:    MaxEnum,
   260  				unique: true,
   261  				ToString: map[int64]string{
   262  					-1: "MERCURY",
   263  					10: "VENUS",
   264  					11: "EARTH",
   265  				},
   266  				ToInt: map[string]int64{
   267  					"MERCURY": -1,
   268  					"VENUS":   10,
   269  					"EARTH":   11,
   270  				},
   271  			},
   272  		},
   273  	}, {
   274  		desc: "enum with repeated specified values",
   275  		in: &Type{
   276  			Name: "enumeration",
   277  			Enum: []*Enum{
   278  				{Name: "MERCURY", Value: &Value{Name: "1"}},
   279  				{Name: "VENUS", Value: &Value{Name: "10"}},
   280  				{Name: "EARTH", Value: &Value{Name: "1"}},
   281  			},
   282  		},
   283  		err: "unknown: fields EARTH and MERCURY conflict on value 1",
   284  	}, {
   285  		desc: "enum with repeated specified names",
   286  		in: &Type{
   287  			Name: "enumeration",
   288  			Enum: []*Enum{
   289  				{Name: "MERCURY", Value: &Value{Name: "-1"}},
   290  				{Name: "VENUS", Value: &Value{Name: "10"}},
   291  				{Name: "MERCURY", Value: &Value{Name: "30"}},
   292  			},
   293  		},
   294  		err: "unknown: field MERCURY already assigned",
   295  	}, {
   296  		desc: "enum with last specified value equal to the max enum value",
   297  		in: &Type{
   298  			Name: "enumeration",
   299  			Enum: []*Enum{
   300  				{Name: "MERCURY", Value: &Value{Name: "-2147483648"}},
   301  				{Name: "VENUS", Value: &Value{Name: "2147483647"}},
   302  				{Name: "EARTH"},
   303  			},
   304  		},
   305  		err: `unknown: enum "EARTH" must specify a value since previous enum is the maximum value allowed`,
   306  	}, {
   307  		desc: "enum value too small",
   308  		in: &Type{
   309  			Name: "enumeration",
   310  			Enum: []*Enum{
   311  				{Name: "MERCURY", Value: &Value{Name: "-2147483649"}},
   312  				{Name: "VENUS", Value: &Value{Name: "0"}},
   313  				{Name: "EARTH"},
   314  			},
   315  		},
   316  		err: `unknown: value -2147483649 for MERCURY too small (minimum is -2147483648)`,
   317  	}, {
   318  		desc: "enum value too large",
   319  		in: &Type{
   320  			Name: "enumeration",
   321  			Enum: []*Enum{
   322  				{Name: "MERCURY", Value: &Value{Name: "-2147483648"}},
   323  				{Name: "VENUS", Value: &Value{Name: "2147483648"}},
   324  				{Name: "EARTH"},
   325  			},
   326  		},
   327  		err: `unknown: value 2147483648 for VENUS too large (maximum is 2147483647)`,
   328  	}, {
   329  		desc: "enum with an unparseable value",
   330  		in: &Type{
   331  			Name: "enumeration",
   332  			Enum: []*Enum{
   333  				{Name: "MERCURY", Value: &Value{Name: "-1"}},
   334  				{Name: "VENUS", Value: &Value{Name: "10"}},
   335  				{Name: "EARTH", Value: &Value{Name: "five"}},
   336  			},
   337  		},
   338  		err: `unknown: strconv.ParseUint: parsing "five": invalid syntax`,
   339  		// TODO(borman): Add in more tests as we honor more fields
   340  		// in Type.
   341  	}}
   342  
   343  	for _, tt := range tests {
   344  		t.Run(tt.desc, func(t *testing.T) {
   345  			// We can initialize a value to ourself, so to it here.
   346  			errs := tt.in.resolve(newTypeDictionary())
   347  
   348  			// TODO(borman):  Do not hack out Root and Base.  These
   349  			// are hacked out for now because they can be self-referential,
   350  			// making construction of them difficult.
   351  			tt.in.YangType.Root = nil
   352  			tt.in.YangType.Base = nil
   353  
   354  			switch {
   355  			case tt.err == "" && len(errs) > 0:
   356  				t.Fatalf("unexpected errors: %v", errs)
   357  			case tt.err != "" && len(errs) == 0:
   358  				t.Fatalf("did not get expected errors: %v", tt.err)
   359  			case len(errs) > 1:
   360  				t.Fatalf("too many errors: %v", errs)
   361  			case len(errs) == 1 && errs[0].Error() != tt.err:
   362  				t.Fatalf("got error %v, want %s", errs[0], tt.err)
   363  			case len(errs) != 0:
   364  				return
   365  			}
   366  
   367  			if diff := cmp.Diff(tt.in.YangType, tt.out); diff != "" {
   368  				t.Errorf("YangType (-got, +want):\n%s", diff)
   369  			}
   370  		})
   371  	}
   372  }
   373  
   374  func TestTypedefResolve(t *testing.T) {
   375  	tests := []struct {
   376  		desc string
   377  		in   *Typedef
   378  		err  string
   379  		out  *YangType
   380  	}{{
   381  		desc: "basic int64",
   382  		in: &Typedef{
   383  			Name:    "time",
   384  			Parent:  baseTypes["int64"].typedef(),
   385  			Default: &Value{Name: "42"},
   386  			Type: &Type{
   387  				Name: "int64",
   388  			},
   389  			Units: &Value{Name: "nanoseconds"},
   390  		},
   391  		out: &YangType{
   392  			Name: "time",
   393  			Kind: Yint64,
   394  			Base: &Type{
   395  				Name: "int64",
   396  			},
   397  			Units:      "nanoseconds",
   398  			Default:    "42",
   399  			HasDefault: true,
   400  			Range:      Int64Range,
   401  		},
   402  	}, {
   403  		desc: "uint32 with more specific range",
   404  		in: &Typedef{
   405  			Name: "another-counter",
   406  			Parent: &Typedef{
   407  				Name:   "counter",
   408  				Parent: baseTypes["uint32"].typedef(),
   409  				Type: &Type{
   410  					Name:  "uint32",
   411  					Range: &Range{Name: "0..42"},
   412  				},
   413  			},
   414  			Type: &Type{
   415  				Name:  "uint32",
   416  				Range: &Range{Name: "10..20"},
   417  			},
   418  		},
   419  		out: &YangType{
   420  			Name: "another-counter",
   421  			Kind: Yuint32,
   422  			Base: &Type{
   423  				Name: "uint32",
   424  			},
   425  			Range: YangRange{{Min: FromInt(10), Max: FromInt(20)}},
   426  		},
   427  		// TODO(wenovus): Add tests on range and length inheritance once those are fixed.
   428  	}}
   429  
   430  	for _, tt := range tests {
   431  		t.Run(tt.desc, func(t *testing.T) {
   432  			// We can initialize a value to ourself, so to it here.
   433  			errs := tt.in.resolve(newTypeDictionary())
   434  
   435  			switch {
   436  			case tt.err == "" && len(errs) > 0:
   437  				t.Fatalf("unexpected errors: %v", errs)
   438  			case tt.err != "" && len(errs) == 0:
   439  				t.Fatalf("did not get expected errors: %v", tt.err)
   440  			case len(errs) > 1:
   441  				t.Fatalf("too many errors: %v", errs)
   442  			case len(errs) == 1 && errs[0].Error() != tt.err:
   443  				t.Fatalf("got error %v, want %s", errs[0], tt.err)
   444  			case len(errs) != 0:
   445  				return
   446  			}
   447  
   448  			if diff := cmp.Diff(tt.in.YangType, tt.out); diff != "" {
   449  				t.Errorf("YangType (-got, +want):\n%s", diff)
   450  			}
   451  		})
   452  	}
   453  }
   454  
   455  func TestTypeResolveUnions(t *testing.T) {
   456  	tests := []struct {
   457  		desc          string
   458  		leafNode      string
   459  		wantType      *testEnumTypeStruct
   460  		wantErrSubstr string
   461  	}{{
   462  		desc: "simple union",
   463  		leafNode: `
   464  			typedef alpha {
   465  				type union {
   466  					type string;
   467  					type uint32;
   468  					type enumeration {
   469  						enum zero;
   470  						enum one;
   471  						enum seven {
   472  							value 7;
   473  						}
   474  					}
   475  				}
   476  			}
   477  
   478  			leaf test-leaf {
   479  				type alpha;
   480  			}
   481  		} // end module`,
   482  		wantType: &testEnumTypeStruct{
   483  			Name: "alpha",
   484  			Type: []*testEnumTypeStruct{{
   485  				Name: "string",
   486  			}, {
   487  				Name: "uint32",
   488  			}, {
   489  				Name:  "enumeration",
   490  				ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0},
   491  			}},
   492  		},
   493  	}, {
   494  		desc: "union with typedef",
   495  		leafNode: `
   496  			typedef alpha {
   497  				type union {
   498  					type string;
   499  					type uint32;
   500  					type enumeration {
   501  						enum zero;
   502  						enum one;
   503  						enum seven {
   504  							value 7;
   505  						}
   506  					}
   507  					type bravo;
   508  				}
   509  			}
   510  
   511  			typedef bravo {
   512  				type union {
   513  					type uint8;
   514  					type uint16;
   515  					type enumeration {
   516  						enum two {
   517  							value 2;
   518  						}
   519  						enum three;
   520  						enum four;
   521  					}
   522  				}
   523  			}
   524  
   525  			leaf test-leaf {
   526  				type alpha;
   527  			}
   528  		} // end module`,
   529  		wantType: &testEnumTypeStruct{
   530  			Name: "alpha",
   531  			Type: []*testEnumTypeStruct{{
   532  				Name: "string",
   533  			}, {
   534  				Name: "uint32",
   535  			}, {
   536  				Name:  "enumeration",
   537  				ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0},
   538  			}, {
   539  				Name: "bravo",
   540  				Type: []*testEnumTypeStruct{{
   541  					Name: "uint8",
   542  				}, {
   543  					Name: "uint16",
   544  				}, {
   545  					Name:  "enumeration",
   546  					ToInt: map[string]int64{"two": 2, "three": 3, "four": 4},
   547  				}},
   548  			}},
   549  		},
   550  	}, {
   551  		desc: "nested unions with typedef",
   552  		leafNode: `
   553  			typedef alpha {
   554  				type union {
   555  					type union {
   556  						type uint32;
   557  						type string;
   558  						type enumeration {
   559  							enum zero;
   560  							enum one;
   561  							enum seven {
   562  								value 7;
   563  							}
   564  						}
   565  					}
   566  					type bravo;
   567  				}
   568  			}
   569  
   570  			typedef bravo {
   571  				type union {
   572  					type uint8;
   573  					type uint16;
   574  					type enumeration {
   575  						enum two {
   576  							value 2;
   577  						}
   578  						enum three;
   579  						enum four;
   580  					}
   581  				}
   582  			}
   583  
   584  			leaf test-leaf {
   585  				type alpha;
   586  			}
   587  		} // end module`,
   588  		wantType: &testEnumTypeStruct{
   589  			Name: "alpha",
   590  			Type: []*testEnumTypeStruct{{
   591  				Name: "union",
   592  				Type: []*testEnumTypeStruct{{
   593  					Name: "uint32",
   594  				}, {
   595  					Name: "string",
   596  				}, {
   597  					Name:  "enumeration",
   598  					ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0},
   599  				}},
   600  			}, {
   601  				Name: "bravo",
   602  				Type: []*testEnumTypeStruct{{
   603  					Name: "uint8",
   604  				}, {
   605  					Name: "uint16",
   606  				}, {
   607  					Name:  "enumeration",
   608  					ToInt: map[string]int64{"two": 2, "three": 3, "four": 4},
   609  				}},
   610  			}},
   611  		},
   612  	}, {
   613  		desc: "simple union with multiple enumerations",
   614  		leafNode: `
   615  			leaf test-leaf {
   616  				type union {
   617  					type string;
   618  					type uint32;
   619  					type enumeration {
   620  						enum zero;
   621  						enum one;
   622  						enum seven {
   623  							value 7;
   624  						}
   625  					}
   626  					type enumeration {
   627  						enum two {
   628  							value 2;
   629  						}
   630  						enum three;
   631  						enum four;
   632  					}
   633  				}
   634  			}
   635  		} // end module`,
   636  		wantType: &testEnumTypeStruct{
   637  			Name: "union",
   638  			Type: []*testEnumTypeStruct{{
   639  				Name: "string",
   640  			}, {
   641  				Name: "uint32",
   642  			}, {
   643  				Name:  "enumeration",
   644  				ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0},
   645  			}, {
   646  				Name:  "enumeration",
   647  				ToInt: map[string]int64{"two": 2, "three": 3, "four": 4},
   648  			}},
   649  		},
   650  	}, {
   651  		desc: "typedef union with multiple enumerations",
   652  		leafNode: `
   653  			typedef alpha {
   654  				type union {
   655  					type string;
   656  					type uint32;
   657  					type enumeration {
   658  						enum zero;
   659  						enum one;
   660  						enum seven {
   661  							value 7;
   662  						}
   663  					}
   664  					type enumeration {
   665  						enum two {
   666  							value 2;
   667  						}
   668  						enum three;
   669  						enum four;
   670  					}
   671  				}
   672  			}
   673  
   674  			leaf test-leaf {
   675  				type alpha;
   676  			}
   677  		} // end module`,
   678  		wantType: &testEnumTypeStruct{
   679  			Name: "alpha",
   680  			Type: []*testEnumTypeStruct{{
   681  				Name: "string",
   682  			}, {
   683  				Name: "uint32",
   684  			}, {
   685  				Name:  "enumeration",
   686  				ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0},
   687  			}, {
   688  				Name:  "enumeration",
   689  				ToInt: map[string]int64{"two": 2, "three": 3, "four": 4},
   690  			}},
   691  		},
   692  	}, {
   693  		desc: "simple union containing typedef union, both with enumerations",
   694  		leafNode: `
   695  			typedef alpha {
   696  				type union {
   697  					type string;
   698  					type uint32;
   699  					type enumeration {
   700  						enum zero;
   701  						enum one;
   702  						enum seven {
   703  							value 7;
   704  						}
   705  					}
   706  				}
   707  			}
   708  
   709  			leaf test-leaf {
   710  				type union {
   711  					type alpha;
   712  					type enumeration {
   713  						enum two {
   714  							value 2;
   715  						}
   716  						enum three;
   717  						enum four;
   718  					}
   719  				}
   720  			}
   721  		} // end module`,
   722  		wantType: &testEnumTypeStruct{
   723  			Name: "union",
   724  			Type: []*testEnumTypeStruct{{
   725  				Name: "alpha",
   726  				Type: []*testEnumTypeStruct{{
   727  					Name: "string",
   728  				}, {
   729  					Name: "uint32",
   730  				}, {
   731  					Name:  "enumeration",
   732  					ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0},
   733  				}},
   734  			}, {
   735  				Name:  "enumeration",
   736  				ToInt: map[string]int64{"two": 2, "three": 3, "four": 4},
   737  			}},
   738  		},
   739  	}, {
   740  		desc: "simple union containing typedef union containing another typedef union, all with multiple simple and typedef enumerations",
   741  		leafNode: `
   742  			typedef a {
   743  				type enumeration {
   744  					enum un {
   745  						value 1;
   746  					}
   747  					enum deux;
   748  				}
   749  			}
   750  
   751  			typedef b {
   752  				type enumeration {
   753  					enum trois {
   754  						value 3;
   755  					}
   756  					enum quatre;
   757  				}
   758  			}
   759  
   760  			typedef c {
   761  				type enumeration {
   762  					enum cinq {
   763  						value 5;
   764  					}
   765  					enum sept {
   766  						value 7;
   767  					}
   768  				}
   769  			}
   770  
   771  			typedef d {
   772  				type enumeration {
   773  					enum huit {
   774  						value 8;
   775  					}
   776  					enum neuf;
   777  				}
   778  			}
   779  
   780  			typedef e {
   781  				type enumeration {
   782  					enum dix {
   783  						value 10;
   784  					}
   785  					enum onze;
   786  				}
   787  			}
   788  
   789  			typedef f {
   790  				type enumeration {
   791  					enum douze {
   792  						value 12;
   793  					}
   794  					enum treize;
   795  				}
   796  			}
   797  
   798  			typedef bravo {
   799  				type union {
   800  					type uint32;
   801  					type enumeration {
   802  						enum eight {
   803  							value 8;
   804  						}
   805  						enum nine;
   806  					}
   807  					type enumeration {
   808  						enum ten {
   809  							value 10;
   810  						}
   811  						enum eleven;
   812  					}
   813  					type e;
   814  					type f;
   815  				}
   816  			}
   817  
   818  			typedef alpha {
   819  				type union {
   820  					type uint16;
   821  					type enumeration {
   822  						enum four {
   823  							value 4;
   824  						}
   825  						enum five;
   826  					}
   827  					type enumeration {
   828  						enum six {
   829  							value 6;
   830  						}
   831  						enum seven;
   832  					}
   833  					type c;
   834  					type d;
   835  					type bravo;
   836  				}
   837  			}
   838  
   839  			leaf test-leaf {
   840  				type union {
   841  					type uint8;
   842  					type enumeration {
   843  						enum zero;
   844  						enum one;
   845  					}
   846  					type enumeration {
   847  						enum two {
   848  							value 2;
   849  						}
   850  						enum three;
   851  					}
   852  					type a;
   853  					type b;
   854  					type alpha;
   855  				}
   856  			}
   857  		} // end module`,
   858  		wantType: &testEnumTypeStruct{
   859  			Name: "union",
   860  			Type: []*testEnumTypeStruct{{
   861  				Name: "uint8",
   862  			}, {
   863  				Name:  "enumeration",
   864  				ToInt: map[string]int64{"zero": 0, "one": 1},
   865  			}, {
   866  				Name:  "enumeration",
   867  				ToInt: map[string]int64{"two": 2, "three": 3},
   868  			}, {
   869  				Name:  "a",
   870  				ToInt: map[string]int64{"un": 1, "deux": 2},
   871  			}, {
   872  				Name:  "b",
   873  				ToInt: map[string]int64{"trois": 3, "quatre": 4},
   874  			}, {
   875  				Name: "alpha",
   876  				Type: []*testEnumTypeStruct{{
   877  					Name: "uint16",
   878  				}, {
   879  					Name:  "enumeration",
   880  					ToInt: map[string]int64{"four": 4, "five": 5},
   881  				}, {
   882  					Name:  "enumeration",
   883  					ToInt: map[string]int64{"six": 6, "seven": 7},
   884  				}, {
   885  					Name:  "c",
   886  					ToInt: map[string]int64{"cinq": 5, "sept": 7},
   887  				}, {
   888  					Name:  "d",
   889  					ToInt: map[string]int64{"huit": 8, "neuf": 9},
   890  				}, {
   891  					Name: "bravo",
   892  					Type: []*testEnumTypeStruct{{
   893  						Name: "uint32",
   894  					}, {
   895  						Name:  "enumeration",
   896  						ToInt: map[string]int64{"eight": 8, "nine": 9},
   897  					}, {
   898  						Name:  "enumeration",
   899  						ToInt: map[string]int64{"ten": 10, "eleven": 11},
   900  					}, {
   901  						Name:  "e",
   902  						ToInt: map[string]int64{"dix": 10, "onze": 11},
   903  					}, {
   904  						Name:  "f",
   905  						ToInt: map[string]int64{"douze": 12, "treize": 13},
   906  					}},
   907  				}},
   908  			}},
   909  		},
   910  	}}
   911  
   912  	getTestLeaf := func(ms *Modules) (*YangType, error) {
   913  		const module = "test"
   914  		m, ok := ms.Modules[module]
   915  		if !ok {
   916  			return nil, fmt.Errorf("can't find module %q", module)
   917  		}
   918  
   919  		if len(m.Leaf) == 0 {
   920  			return nil, fmt.Errorf("node %v is missing imports", m)
   921  		}
   922  		e := ToEntry(m)
   923  		return e.Dir["test-leaf"].Type, nil
   924  	}
   925  
   926  	for _, tt := range tests {
   927  		inModules := map[string]string{
   928  			"test": `
   929  				module test {
   930  					prefix "t";
   931  					namespace "urn:t";
   932  
   933  					` + tt.leafNode,
   934  		}
   935  
   936  		t.Run(tt.desc, func(t *testing.T) {
   937  			ms := NewModules()
   938  			for n, m := range inModules {
   939  				if err := ms.Parse(m, n); err != nil {
   940  					t.Fatalf("error parsing module %s, got: %v, want: nil", n, err)
   941  				}
   942  			}
   943  			errs := ms.Process()
   944  			var err error
   945  			if len(errs) > 1 {
   946  				t.Fatalf("Got more than 1 error: %v", errs)
   947  			} else if len(errs) == 1 {
   948  				err = errs[0]
   949  			}
   950  			if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" {
   951  				t.Errorf("Did not get expected error: %s", diff)
   952  			}
   953  			if err != nil {
   954  				return
   955  			}
   956  
   957  			gotType, err := getTestLeaf(ms)
   958  			if err != nil {
   959  				t.Fatal(err)
   960  			}
   961  
   962  			if diff := cmp.Diff(filterTypeNames(gotType), tt.wantType); diff != "" {
   963  				t.Errorf("Type.resolve() union types test (-got, +want):\n%s", diff)
   964  			}
   965  		})
   966  	}
   967  }
   968  
   969  type testEnumTypeStruct struct {
   970  	Name string
   971  	// ToInt is the ToInt map representing the enum value (if present).
   972  	ToInt map[string]int64
   973  	Type  []*testEnumTypeStruct
   974  }
   975  
   976  // filterTypeNames returns a testEnumTypeStruct with only the
   977  // YangType.Name fields of the given type, preserving
   978  // the recursive structure of the type, to work around cmp not
   979  // having an allowlist way of specifying which fields to
   980  // compare and YangType having a custom Equal function.
   981  func filterTypeNames(ytype *YangType) *testEnumTypeStruct {
   982  	filteredNames := &testEnumTypeStruct{Name: ytype.Name}
   983  	if ytype.Enum != nil {
   984  		filteredNames.ToInt = ytype.Enum.ToInt
   985  	}
   986  	for _, subtype := range ytype.Type {
   987  		filteredNames.Type = append(filteredNames.Type, filterTypeNames(subtype))
   988  	}
   989  	return filteredNames
   990  }
   991  
   992  func TestPattern(t *testing.T) {
   993  	tests := []struct {
   994  		desc          string
   995  		leafNode      string
   996  		wantType      *YangType
   997  		wantErrSubstr string
   998  	}{{
   999  		desc: "Only normal patterns",
  1000  		leafNode: `
  1001  			leaf test-leaf {
  1002  				type string {
  1003  					o:bar 'coo';
  1004  					o:bar 'foo';
  1005  					pattern 'charlie';
  1006  					o:bar 'goo';
  1007  				}
  1008  			}
  1009  		} // end module`,
  1010  		wantType: &YangType{
  1011  			Pattern: []string{"charlie"},
  1012  		},
  1013  	}, {
  1014  		desc: "Only posix patterns",
  1015  		leafNode: `
  1016  			leaf test-leaf {
  1017  				type string {
  1018  					o:bar 'coo';
  1019  					o:posix-pattern 'bravo';
  1020  					o:bar 'foo';
  1021  					o:posix-pattern 'charlie';
  1022  					o:bar 'goo';
  1023  				}
  1024  			}
  1025  		} // end module`,
  1026  		wantType: &YangType{
  1027  			POSIXPattern: []string{"bravo", "charlie"},
  1028  		},
  1029  	}, {
  1030  		desc: "No patterns",
  1031  		leafNode: `
  1032  			leaf test-leaf {
  1033  				type string;
  1034  			}
  1035  		}`,
  1036  		wantType: &YangType{
  1037  			Pattern:      nil,
  1038  			POSIXPattern: nil,
  1039  		},
  1040  	}, {
  1041  		desc: "Both patterns",
  1042  		leafNode: `
  1043  			leaf test-leaf {
  1044  				type string {
  1045  					pattern 'alpha';
  1046  					o:posix-pattern 'bravo';
  1047  					o:posix-pattern 'charlie';
  1048  					o:bar 'coo';
  1049  					o:posix-pattern 'delta';
  1050  				}
  1051  			}
  1052  		} // end module`,
  1053  		wantType: &YangType{
  1054  			Pattern:      []string{"alpha"},
  1055  			POSIXPattern: []string{"bravo", "charlie", "delta"},
  1056  		},
  1057  	}, {
  1058  		desc: "Both patterns, but with non-openconfig-extensions pretenders",
  1059  		leafNode: `
  1060  			leaf test-leaf {
  1061  				type string {
  1062  					pattern 'alpha';
  1063  					o:bar 'coo';
  1064  					o:posix-pattern 'delta';
  1065  
  1066  					n:posix-pattern 'golf';
  1067  
  1068  					pattern 'bravo';
  1069  					o:bar 'foo';
  1070  					o:posix-pattern 'echo';
  1071  
  1072  					pattern 'charlie';
  1073  					o:bar 'goo';
  1074  					o:posix-pattern 'foxtrot';
  1075  
  1076  					n:posix-pattern 'hotel';
  1077  				}
  1078  			}
  1079  		} // end module`,
  1080  		wantType: &YangType{
  1081  			Pattern:      []string{"alpha", "bravo", "charlie"},
  1082  			POSIXPattern: []string{"delta", "echo", "foxtrot"},
  1083  		},
  1084  	}, {
  1085  		desc: "Union type",
  1086  		leafNode: `
  1087  			leaf test-leaf {
  1088  				type union {
  1089  					type string {
  1090  						pattern 'alpha';
  1091  						o:bar 'coo';
  1092  						o:posix-pattern 'delta';
  1093  
  1094  						pattern 'bravo';
  1095  						o:bar 'foo';
  1096  						o:posix-pattern 'echo';
  1097  						n:posix-pattern 'echo2';
  1098  
  1099  						pattern 'charlie';
  1100  						o:bar 'goo';
  1101  						o:posix-pattern 'foxtrot';
  1102  					}
  1103  					type uint64;
  1104  				}
  1105  			}
  1106  		} // end module`,
  1107  		wantType: &YangType{
  1108  			Type: []*YangType{{
  1109  				Pattern:      []string{"alpha", "bravo", "charlie"},
  1110  				POSIXPattern: []string{"delta", "echo", "foxtrot"},
  1111  			}, {
  1112  				Pattern:      nil,
  1113  				POSIXPattern: nil,
  1114  			}},
  1115  		},
  1116  	}, {
  1117  		desc: "Union type -- de-duping string types",
  1118  		leafNode: `
  1119  			leaf test-leaf {
  1120  				type union {
  1121  					type string {
  1122  						pattern 'alpha';
  1123  						o:posix-pattern 'alpha';
  1124  					}
  1125  					type string {
  1126  						pattern 'alpha';
  1127  						o:posix-pattern 'alpha';
  1128  					}
  1129  				}
  1130  			}
  1131  		} // end module`,
  1132  		wantType: &YangType{
  1133  			Type: []*YangType{{
  1134  				Pattern:      []string{"alpha"},
  1135  				POSIXPattern: []string{"alpha"},
  1136  			}},
  1137  		},
  1138  	}, {
  1139  		desc: "Union type -- different string types due to different patterns",
  1140  		leafNode: `
  1141  			leaf test-leaf {
  1142  				type union {
  1143  					type string {
  1144  						pattern 'alpha';
  1145  					}
  1146  					type string {
  1147  						pattern 'bravo';
  1148  					}
  1149  				}
  1150  			}
  1151  		} // end module`,
  1152  		wantType: &YangType{
  1153  			Type: []*YangType{{
  1154  				Pattern: []string{"alpha"},
  1155  			}, {
  1156  				Pattern: []string{"bravo"},
  1157  			}},
  1158  		},
  1159  	}, {
  1160  		desc: "Union type -- different string types due to different posix-patterns",
  1161  		leafNode: `
  1162  			leaf test-leaf {
  1163  				type union {
  1164  					type string {
  1165  						o:posix-pattern 'alpha';
  1166  					}
  1167  					type string {
  1168  						o:posix-pattern 'bravo';
  1169  					}
  1170  				}
  1171  			}
  1172  		} // end module`,
  1173  		wantType: &YangType{
  1174  			Type: []*YangType{{
  1175  				POSIXPattern: []string{"alpha"},
  1176  			}, {
  1177  				POSIXPattern: []string{"bravo"},
  1178  			}},
  1179  		},
  1180  	}, {
  1181  		desc: "typedef",
  1182  		leafNode: `
  1183  			leaf test-leaf {
  1184  				type leaf-type;
  1185  			}
  1186  
  1187  			typedef leaf-type {
  1188  				type string {
  1189  					pattern 'alpha';
  1190  					o:bar 'coo';
  1191  					o:posix-pattern 'delta';
  1192  
  1193  					pattern 'bravo';
  1194  					o:bar 'foo';
  1195  					o:posix-pattern 'echo';
  1196  
  1197  					pattern 'charlie';
  1198  					o:bar 'goo';
  1199  					o:posix-pattern 'foxtrot';
  1200  				}
  1201  			}
  1202  		} // end module`,
  1203  		wantType: &YangType{
  1204  			Pattern:      []string{"alpha", "bravo", "charlie"},
  1205  			POSIXPattern: []string{"delta", "echo", "foxtrot"},
  1206  		},
  1207  	}, {
  1208  		desc: "invalid POSIX pattern",
  1209  		leafNode: `
  1210  			leaf test-leaf {
  1211  				type leaf-type;
  1212  			}
  1213  
  1214  			typedef leaf-type {
  1215  				type string {
  1216  					o:posix-pattern '?';
  1217  				}
  1218  			}
  1219  		} // end module`,
  1220  		wantErrSubstr: "bad pattern",
  1221  	}}
  1222  
  1223  	getTestLeaf := func(ms *Modules) (*YangType, error) {
  1224  		const module = "test"
  1225  		m, ok := ms.Modules[module]
  1226  		if !ok {
  1227  			return nil, fmt.Errorf("can't find module %q", module)
  1228  		}
  1229  
  1230  		if len(m.Leaf) == 0 {
  1231  			return nil, fmt.Errorf("node %v is missing imports", m)
  1232  		}
  1233  		e := ToEntry(m)
  1234  		return e.Dir["test-leaf"].Type, nil
  1235  	}
  1236  
  1237  	for _, tt := range tests {
  1238  		inModules := map[string]string{
  1239  			"test": `
  1240  				module test {
  1241  					prefix "t";
  1242  					namespace "urn:t";
  1243  
  1244  					import non-openconfig-extensions {
  1245  						prefix "n";
  1246  						description "non-openconfig-extensions module";
  1247  					}
  1248  					import openconfig-extensions {
  1249  						prefix "o";
  1250  						description "openconfig-extensions module";
  1251  					}` + tt.leafNode,
  1252  			"openconfig-extensions": `
  1253  				module openconfig-extensions {
  1254  					prefix "o";
  1255  					namespace "urn:o";
  1256  
  1257  					extension bar {
  1258  						argument "baz";
  1259  					}
  1260  
  1261  					extension posix-pattern {
  1262  						argument "pattern";
  1263  					}
  1264  				}
  1265  			`,
  1266  			"non-openconfig-extensions": `
  1267  				module non-openconfig-extensions {
  1268  					prefix "n";
  1269  					namespace "urn:n";
  1270  
  1271  					extension bar {
  1272  						argument "baz";
  1273  					}
  1274  
  1275  					extension posix-pattern {
  1276  						argument "pattern";
  1277  					}
  1278  				}
  1279  			`,
  1280  		}
  1281  
  1282  		t.Run(tt.desc, func(t *testing.T) {
  1283  			ms := NewModules()
  1284  			for n, m := range inModules {
  1285  				if err := ms.Parse(m, n); err != nil {
  1286  					t.Fatalf("error parsing module %s, got: %v, want: nil", n, err)
  1287  				}
  1288  			}
  1289  			errs := ms.Process()
  1290  			var err error
  1291  			if len(errs) > 1 {
  1292  				t.Fatalf("Got more than 1 error: %v", errs)
  1293  			} else if len(errs) == 1 {
  1294  				err = errs[0]
  1295  			}
  1296  			if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" {
  1297  				t.Errorf("Did not get expected error: %s", diff)
  1298  			}
  1299  			if err != nil {
  1300  				return
  1301  			}
  1302  
  1303  			yangType, err := getTestLeaf(ms)
  1304  			if err != nil {
  1305  				t.Fatal(err)
  1306  			}
  1307  
  1308  			gotType := &YangType{}
  1309  			populatePatterns(yangType, gotType)
  1310  			if diff := cmp.Diff(gotType, tt.wantType, cmpopts.EquateEmpty()); diff != "" {
  1311  				t.Errorf("Type.resolve() pattern test (-got, +want):\n%s", diff)
  1312  			}
  1313  		})
  1314  	}
  1315  }
  1316  
  1317  // populatePatterns populates targetType with only the
  1318  // Pattern/POSIXPattern fields of the given type, preserving
  1319  // the recursive structure of the type, to work around cmp not
  1320  // having an allowlist way of specifying which fields to
  1321  // compare.
  1322  func populatePatterns(ytype *YangType, targetType *YangType) {
  1323  	targetType.Pattern = ytype.Pattern
  1324  	targetType.POSIXPattern = ytype.POSIXPattern
  1325  	for _, subtype := range ytype.Type {
  1326  		targetSubtype := &YangType{}
  1327  		targetType.Type = append(targetType.Type, targetSubtype)
  1328  		populatePatterns(subtype, targetSubtype)
  1329  	}
  1330  }
  1331  
  1332  func TestTypeLengthRange(t *testing.T) {
  1333  	tests := []struct {
  1334  		desc          string
  1335  		leafNode      string
  1336  		wantType      *testRangeTypeStruct
  1337  		wantErrSubstr string
  1338  	}{{
  1339  		desc: "simple uint32",
  1340  		leafNode: `
  1341  			typedef alpha {
  1342  				type uint32 {
  1343  					range "1..4 | 10..20";
  1344  				}
  1345  			}
  1346  			leaf test-leaf {
  1347  				type alpha;
  1348  			}
  1349  		} // end module`,
  1350  		wantType: &testRangeTypeStruct{
  1351  			Name:  "alpha",
  1352  			Range: YangRange{R(1, 4), R(10, 20)},
  1353  		},
  1354  	}, {
  1355  		desc: "inherited uint32",
  1356  		leafNode: `
  1357  			typedef alpha {
  1358  				type uint32 {
  1359  					range "1..4 | 10..20";
  1360  				}
  1361  			}
  1362  			typedef bravo {
  1363  				type alpha {
  1364  					range "min..3 | 12..max";
  1365  				}
  1366  			}
  1367  			leaf test-leaf {
  1368  				type bravo;
  1369  			}
  1370  		} // end module`,
  1371  		wantType: &testRangeTypeStruct{
  1372  			Name:  "bravo",
  1373  			Range: YangRange{R(1, 3), R(12, 20)},
  1374  		},
  1375  	}, {
  1376  		desc: "inherited uint32 range violation",
  1377  		leafNode: `
  1378  			typedef alpha {
  1379  				type uint32 {
  1380  					range "1..4 | 10..20";
  1381  				}
  1382  			}
  1383  			typedef bravo {
  1384  				type alpha {
  1385  					range "min..max";
  1386  				}
  1387  			}
  1388  			leaf test-leaf {
  1389  				type bravo;
  1390  			}
  1391  		} // end module`,
  1392  		wantErrSubstr: "not within",
  1393  	}, {
  1394  		desc: "unrestricted decimal64",
  1395  		leafNode: `
  1396  			typedef alpha {
  1397  				type decimal64 {
  1398  					fraction-digits 2;
  1399  				}
  1400  			}
  1401  			leaf test-leaf {
  1402  				type alpha;
  1403  			}
  1404  		} // end module`,
  1405  		wantType: &testRangeTypeStruct{
  1406  			Name:  "alpha",
  1407  			Range: YangRange{Rf(MinInt64, MaxInt64, 2)},
  1408  		},
  1409  	}, {
  1410  		desc: "simple restricted decimal64",
  1411  		leafNode: `
  1412  			typedef alpha {
  1413  				type decimal64 {
  1414  					fraction-digits 2;
  1415  					range "1 .. 3.14 | 10 | 20..max";
  1416  				}
  1417  			}
  1418  			leaf test-leaf {
  1419  				type alpha;
  1420  			}
  1421  		} // end module`,
  1422  		wantType: &testRangeTypeStruct{
  1423  			Name:  "alpha",
  1424  			Range: YangRange{Rf(100, 314, 2), Rf(1000, 1000, 2), Rf(2000, MaxInt64, 2)},
  1425  		},
  1426  	}, {
  1427  		desc: "simple decimal64 with inherited ranges",
  1428  		leafNode: `
  1429  			typedef alpha {
  1430  				type decimal64 {
  1431  					fraction-digits 3;
  1432  					range "1 .. 3.14 | 10 | 20..max";
  1433  				}
  1434  			}
  1435  			typedef bravo {
  1436  				type alpha {
  1437  					range "min .. 2.72 | 42 .. max";
  1438  				}
  1439  			}
  1440  			leaf test-leaf {
  1441  				type bravo;
  1442  			}
  1443  		} // end module`,
  1444  		wantType: &testRangeTypeStruct{
  1445  			Name:  "bravo",
  1446  			Range: YangRange{Rf(1000, 2720, 3), Rf(42000, MaxInt64, 3)},
  1447  		},
  1448  	}, {
  1449  		desc: "triple-inherited decimal64",
  1450  		leafNode: `
  1451  			typedef alpha {
  1452  				type decimal64 {
  1453  					fraction-digits 2;
  1454  				}
  1455  			}
  1456  			typedef bravo {
  1457  				type alpha {
  1458  					range "1 .. 3.14 | 10 | 20..max";
  1459  				}
  1460  			}
  1461  			typedef charlie {
  1462  				type bravo {
  1463  					range "min .. 2.72 | 42 .. max";
  1464  				}
  1465  			}
  1466  			leaf test-leaf {
  1467  				type charlie;
  1468  			}
  1469  		} // end module`,
  1470  		wantType: &testRangeTypeStruct{
  1471  			Name:  "charlie",
  1472  			Range: YangRange{Rf(100, 272, 2), Rf(4200, MaxInt64, 2)},
  1473  		},
  1474  	}, {
  1475  		desc: "simple decimal64 with inherited ranges",
  1476  		leafNode: `
  1477  			typedef alpha {
  1478  				type decimal64 {
  1479  					fraction-digits 2;
  1480  					range "1 .. 3.14 | 10 | 20..max";
  1481  				}
  1482  			}
  1483  			typedef bravo {
  1484  				type alpha {
  1485  					range "min..max";
  1486  				}
  1487  			}
  1488  			leaf test-leaf {
  1489  				type alpha;
  1490  			}
  1491  		} // end module`,
  1492  		wantErrSubstr: "not within",
  1493  	}, {
  1494  		desc: "simple decimal64 with too few fractional digits",
  1495  		leafNode: `
  1496  			typedef alpha {
  1497  				type decimal64 {
  1498  					fraction-digits 1;
  1499  					range "1 .. 3.14 | 10 | 20..max";
  1500  				}
  1501  			}
  1502  			leaf test-leaf {
  1503  				type alpha;
  1504  			}
  1505  		} // end module`,
  1506  		wantErrSubstr: "has too much precision",
  1507  	}, {
  1508  		desc: "simple decimal64 fractional digit on inherited decimal64 type",
  1509  		leafNode: `
  1510  			typedef alpha {
  1511  				type decimal64 {
  1512  					fraction-digits 2;
  1513  					range "1 .. 3.14 | 10 | 20..max";
  1514  				}
  1515  			}
  1516  			typedef bravo {
  1517  				type alpha {
  1518  					fraction-digits 2;
  1519  					range "25..max";
  1520  				}
  1521  			}
  1522  			leaf test-leaf {
  1523  				type bravo;
  1524  			}
  1525  		} // end module`,
  1526  		wantErrSubstr: "overriding of fraction-digits not allowed",
  1527  	}, {
  1528  		desc: "simple string with length",
  1529  		leafNode: `
  1530  			typedef alpha {
  1531  				type string {
  1532  					length "1..4 | 10..20 | 30..max";
  1533  				}
  1534  			}
  1535  			leaf test-leaf {
  1536  				type alpha;
  1537  			}
  1538  		} // end module`,
  1539  		wantType: &testRangeTypeStruct{
  1540  			Name:   "alpha",
  1541  			Length: YangRange{R(1, 4), R(10, 20), YRange{FromInt(30), FromUint(maxUint64)}},
  1542  		},
  1543  	}, {
  1544  		desc: "inherited string",
  1545  		leafNode: `
  1546  			typedef alpha {
  1547  				type string {
  1548  					length "1..4 | 10..20 | 30..max";
  1549  				}
  1550  			}
  1551  			typedef bravo {
  1552  				type alpha {
  1553  					length "min..3 | 42..max";
  1554  				}
  1555  			}
  1556  			leaf test-leaf {
  1557  				type bravo;
  1558  			}
  1559  		} // end module`,
  1560  		wantType: &testRangeTypeStruct{
  1561  			Name:   "bravo",
  1562  			Length: YangRange{R(1, 3), YRange{FromInt(42), FromUint(maxUint64)}},
  1563  		},
  1564  	}, {
  1565  		desc: "inherited binary",
  1566  		leafNode: `
  1567  			typedef alpha {
  1568  				type binary {
  1569  					length "1..4 | 10..20 | 30..max";
  1570  				}
  1571  			}
  1572  			typedef bravo {
  1573  				type alpha {
  1574  					length "min..3 | 42..max";
  1575  				}
  1576  			}
  1577  			leaf test-leaf {
  1578  				type bravo;
  1579  			}
  1580  		} // end module`,
  1581  		wantType: &testRangeTypeStruct{
  1582  			Name:   "bravo",
  1583  			Length: YangRange{R(1, 3), YRange{FromInt(42), FromUint(maxUint64)}},
  1584  		},
  1585  	}, {
  1586  		desc: "inherited string length violation",
  1587  		leafNode: `
  1588  			typedef alpha {
  1589  				type string {
  1590  					length "1..4 | 10..20 | 30..max";
  1591  				}
  1592  			}
  1593  			typedef bravo {
  1594  				type alpha {
  1595  					length "min..max";
  1596  				}
  1597  			}
  1598  			leaf test-leaf {
  1599  				type bravo;
  1600  			}
  1601  		} // end module`,
  1602  		wantErrSubstr: "not within",
  1603  	}, {
  1604  		desc: "simple union",
  1605  		leafNode: `
  1606  				typedef alpha {
  1607  					type union {
  1608  						type string;
  1609  						type binary {
  1610  							length "min..5|999..max";
  1611  						}
  1612  						type int8 {
  1613  							range "min..-42|42..max";
  1614  						}
  1615  						type enumeration {
  1616  							enum zero;
  1617  							enum one;
  1618  							enum seven {
  1619  								value 7;
  1620  							}
  1621  						}
  1622  					}
  1623  				}
  1624  				leaf test-leaf {
  1625  					type alpha;
  1626  				}
  1627  			} // end module`,
  1628  		wantType: &testRangeTypeStruct{
  1629  			Name: "alpha",
  1630  			Type: []*testRangeTypeStruct{{
  1631  				Name: "string",
  1632  			}, {
  1633  				Name:   "binary",
  1634  				Length: YangRange{R(0, 5), YRange{FromInt(999), FromUint(maxUint64)}},
  1635  			}, {
  1636  				Name:  "int8",
  1637  				Range: YangRange{R(minInt8, -42), R(42, maxInt8)},
  1638  			}, {
  1639  				Name: "enumeration",
  1640  			}},
  1641  		},
  1642  	}}
  1643  
  1644  	getTestLeaf := func(ms *Modules) (*YangType, error) {
  1645  		const moduleName = "test"
  1646  		m, ok := ms.Modules[moduleName]
  1647  		if !ok {
  1648  			return nil, fmt.Errorf("module not found: %q", moduleName)
  1649  		}
  1650  		if len(m.Leaf) == 0 {
  1651  			return nil, fmt.Errorf("node %v is missing imports", m)
  1652  		}
  1653  		e := ToEntry(m)
  1654  		return e.Dir["test-leaf"].Type, nil
  1655  	}
  1656  
  1657  	for _, tt := range tests {
  1658  		inModules := map[string]string{
  1659  			"test": `
  1660  				module test {
  1661  					prefix "t";
  1662  					namespace "urn:t";
  1663  					` + tt.leafNode,
  1664  		}
  1665  
  1666  		t.Run(tt.desc, func(t *testing.T) {
  1667  			ms := NewModules()
  1668  			for n, m := range inModules {
  1669  				if err := ms.Parse(m, n); err != nil {
  1670  					t.Fatalf("error parsing module %s, got: %v, want: nil", n, err)
  1671  				}
  1672  			}
  1673  			errs := ms.Process()
  1674  			var err error
  1675  			if len(errs) > 1 {
  1676  				t.Fatalf("Got more than 1 error: %v", errs)
  1677  			} else if len(errs) == 1 {
  1678  				err = errs[0]
  1679  			}
  1680  			if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" {
  1681  				t.Errorf("Did not get expected error: %s", diff)
  1682  			}
  1683  			if err != nil {
  1684  				return
  1685  			}
  1686  
  1687  			gotType, err := getTestLeaf(ms)
  1688  			if err != nil {
  1689  				t.Fatal(err)
  1690  			}
  1691  
  1692  			if diff := cmp.Diff(filterRanges(gotType), tt.wantType); diff != "" {
  1693  				t.Errorf("Type.resolve() union types test (-got, +want):\n%s", diff)
  1694  			}
  1695  		})
  1696  	}
  1697  }
  1698  
  1699  // testRangeTypeStruct is a filtered-down version of YangType where only certain
  1700  // fields are preserved for targeted testing.
  1701  type testRangeTypeStruct struct {
  1702  	Name   string
  1703  	Length YangRange
  1704  	Range  YangRange
  1705  	Type   []*testRangeTypeStruct
  1706  }
  1707  
  1708  // filterRanges returns a testRangeTypeStruct with only the Name, Length, and Range
  1709  // fields of the given YangType, preserving the recursive structure of the
  1710  // type, to work around cmp not having an allowlist way of specifying which
  1711  // fields to compare and YangType having a custom Equal function.
  1712  func filterRanges(ytype *YangType) *testRangeTypeStruct {
  1713  	filteredType := &testRangeTypeStruct{Name: ytype.Name}
  1714  	filteredType.Length = ytype.Length
  1715  	filteredType.Range = ytype.Range
  1716  	for _, subtype := range ytype.Type {
  1717  		filteredType.Type = append(filteredType.Type, filterRanges(subtype))
  1718  	}
  1719  	return filteredType
  1720  }