Repository: openconfig/goyang
Pull request #130 (2020-06-28 22:55:36)

Add support for multiple `base` substatements in `identity` statement
Squashed commit of the following:

commit 2cd89ac3d2344838867d446393e6e5174dca7700
Author: Herman Slatman <hermanslatman@hotmail.com>
Date:   Mon Jun 29 00:20:41 2020 +0200

    Fix existing tests that assume a single `base`

commit 528bac09ed575c111335a5d6b3589a5ab4e5c456
Author: Herman Slatman <hermanslatman@hotmail.com>
Date:   Mon Jun 29 00:14:23 2020 +0200

    Change `base` to be a slice

commit d1f5ccb6c756769739f92618e3a6149819a5526e
Author: Herman Slatman <hermanslatman@hotmail.com>
Date:   Mon Jun 29 00:08:06 2020 +0200

    Add support for multiple `base` substatements in `identity` statement


Patch:
diff --git a/pkg/yang/identity.go b/pkg/yang/identity.go
index e6bfd6b..7bc7cf1 100644
--- a/pkg/yang/identity.go
+++ b/pkg/yang/identity.go
@@ -168,18 +168,20 @@ func (ms *Modules) resolveIdentities() []error {
 	// that have a base, so that we can do inheritance of these later.
 	for _, i := range identities.dict {
 		if i.Identity.Base != nil {
-			// This identity inherits from another identity.
+			// This identity inherits from one or more other identities.
 
 			root := RootNode(i.Identity)
-			base, baseErr := root.findIdentityBase(i.Identity.Base.asString())
+			for _, b := range i.Identity.Base {
+				base, baseErr := root.findIdentityBase(b.asString())
 
-			if baseErr != nil {
-				errs = append(errs, baseErr...)
-				continue
-			}
+				if baseErr != nil {
+					errs = append(errs, baseErr...)
+					continue
+				}
 
-			// Append this value to the children of the base identity.
-			base.Identity.Values = append(base.Identity.Values, i.Identity)
+				// Append this value to the children of the base identity.
+				base.Identity.Values = append(base.Identity.Values, i.Identity)
+			}
 		}
 	}
 
diff --git a/pkg/yang/yang.go b/pkg/yang/yang.go
index 809e5b5..a04b187 100644
--- a/pkg/yang/yang.go
+++ b/pkg/yang/yang.go
@@ -818,7 +818,7 @@ type Identity struct {
 	Parent     Node         `yang:"Parent,nomerge" json:"-"`
 	Extensions []*Statement `yang:"Ext" json:"-"`
 
-	Base        *Value      `yang:"base" json:"-"`
+	Base        []*Value    `yang:"base" json:"-"`
 	Description *Value      `yang:"description" json:"-"`
 	IfFeature   []*Value    `yang:"if-feature" json:"-"`
 	Reference   *Value      `yang:"reference" json:"-"`


Test patch:
diff --git a/pkg/yang/identity_test.go b/pkg/yang/identity_test.go
index c0f33f2..68225df 100644
--- a/pkg/yang/identity_test.go
+++ b/pkg/yang/identity_test.go
@@ -17,6 +17,8 @@ package yang
 import (
 	"reflect"
 	"testing"
+
+	"github.com/google/go-cmp/cmp"
 )
 
 // inputModule is a mock input YANG module.
@@ -33,10 +35,10 @@ type idrefOut struct {
 
 // identityOut is the output for a particular identity within the test case.
 type identityOut struct {
-	module   string   // The module that the identity is within.
-	name     string   // The name of the identity.
-	baseName string   // The base of the identity as a string.
-	values   []string // The string names of derived identities.
+	module    string   // The module that the identity is within.
+	name      string   // The name of the identity.
+	baseNames []string // The base(s) of the identity as string(s).
+	values    []string // The string names of derived identities.
 }
 
 // identityTestCase is a test case for a module which contains identities.
@@ -48,6 +50,15 @@ type identityTestCase struct {
 	err        string        // Test case error string
 }
 
+// getBaseNamesFrom is a utility function for getting the base name(s) of an identity
+func getBaseNamesFrom(i *Identity) []string {
+	baseNames := []string{}
+	for _, base := range i.Base {
+		baseNames = append(baseNames, base.Name)
+	}
+	return baseNames
+}
+
 // Test cases for basic identity extraction.
 var basicTestCases = []identityTestCase{
 	{
@@ -90,10 +101,36 @@ var basicTestCases = []identityTestCase{
 		identities: []identityOut{
 			{module: "idtest-two", name: "TEST_ID"},
 			{module: "idtest-two", name: "TEST_ID_TWO"},
-			{module: "idtest-two", name: "TEST_CHILD", baseName: "TEST_ID"},
+			{module: "idtest-two", name: "TEST_CHILD", baseNames: []string{"TEST_ID"}},
 		},
 		err: "basic-test-case-2: could not resolve identities",
 	},
+	{
+		name: "basic-test-case-3: Check identity with multiple bases.",
+		in: []inputModule{
+			{
+				name: "idtest-three",
+				content: `
+					module idtest-three {
+					  namespace "urn:idthree";
+					  prefix "idthree";
+
+					  identity BASE_ONE;
+					  identity BASE_TWO;
+					  identity TEST_CHILD_WITH_MULTIPLE_BASES {
+						base BASE_ONE;
+						base BASE_TWO;
+					  }
+					}
+				`},
+		},
+		identities: []identityOut{
+			{module: "idtest-three", name: "BASE_ONE"},
+			{module: "idtest-three", name: "BASE_TWO"},
+			{module: "idtest-three", name: "TEST_CHILD_WITH_MULTIPLE_BASES", baseNames: []string{"BASE_ONE", "BASE_TWO"}},
+		},
+		err: "basic-test-case-3: could not resolve identities",
+	},
 }
 
 // Test the ability to extract identities from a module with the correct base
@@ -127,15 +164,15 @@ func TestIdentityExtract(t *testing.T) {
 				t.Errorf("Could not found identity %s in %s", ti.name, ti.module)
 			}
 
-			if ti.baseName != "" {
-				if ti.baseName != thisID.Base.Name {
-					t.Errorf("Identity %s did not have expected base %s, had %s", ti.name,
-						ti.baseName, thisID.Base.Name)
+			actualBaseNames := getBaseNamesFrom(thisID)
+			if len(ti.baseNames) > 0 {
+				if diff := cmp.Diff(actualBaseNames, ti.baseNames); diff != "" {
+					t.Errorf("(-got, +want):\n%s", diff)
 				}
 			} else {
 				if thisID.Base != nil {
-					t.Errorf("Identity %s had an unexpected base %s", thisID.Name,
-						thisID.Base.Name)
+					t.Errorf("Identity %s had unexpected base(s) %s", thisID.Name,
+						actualBaseNames)
 				}
 			}
 		}
@@ -179,9 +216,9 @@ var treeTestCases = []identityTestCase{
 				values: []string{"LOCAL_REMOTE_BASE"},
 			},
 			{
-				module:   "base",
-				name:     "LOCAL_REMOTE_BASE",
-				baseName: "r:REMOTE_BASE",
+				module:    "base",
+				name:      "LOCAL_REMOTE_BASE",
+				baseNames: []string{"r:REMOTE_BASE"},
 			},
 		},
 	},
@@ -231,31 +268,31 @@ var treeTestCases = []identityTestCase{
 				},
 			},
 			{
-				module:   "base",
-				name:     "GRANDFATHER",
-				baseName: "GREATGRANDFATHER",
-				values:   []string{"FATHER", "UNCLE", "SON", "BROTHER"},
+				module:    "base",
+				name:      "GRANDFATHER",
+				baseNames: []string{"GREATGRANDFATHER"},
+				values:    []string{"FATHER", "UNCLE", "SON", "BROTHER"},
 			},
 			{
-				module:   "base",
-				name:     "GREATUNCLE",
-				baseName: "GREATGRANDFATHER",
+				module:    "base",
+				name:      "GREATUNCLE",
+				baseNames: []string{"GREATGRANDFATHER"},
 			},
 			{
-				module:   "base",
-				name:     "FATHER",
-				baseName: "GRANDFATHER",
-				values:   []string{"SON", "BROTHER"},
+				module:    "base",
+				name:      "FATHER",
+				baseNames: []string{"GRANDFATHER"},
+				values:    []string{"SON", "BROTHER"},
 			},
 			{
-				module:   "base",
-				name:     "UNCLE",
-				baseName: "GRANDFATHER",
+				module:    "base",
+				name:      "UNCLE",
+				baseNames: []string{"GRANDFATHER"},
 			},
 			{
-				module:   "base",
-				name:     "BROTHER",
-				baseName: "FATHER",
+				module:    "base",
+				name:      "BROTHER",
+				baseNames: []string{"FATHER"},
 			},
 		},
 	},
@@ -288,9 +325,9 @@ var treeTestCases = []identityTestCase{
 				values: []string{"NOTBASE"},
 			},
 			{
-				module:   "base",
-				name:     "NOTBASE",
-				baseName: "BASE",
+				module:    "base",
+				name:      "NOTBASE",
+				baseNames: []string{"BASE"},
 			},
 		},
 		idrefs: []idrefOut{
@@ -334,9 +371,9 @@ var treeTestCases = []identityTestCase{
 				values: []string{"CHILD4"},
 			},
 			{
-				module:   "base4",
-				name:     "CHILD4",
-				baseName: "BASE4",
+				module:    "base4",
+				name:      "CHILD4",
+				baseNames: []string{"BASE4"},
 			},
 		},
 		idrefs: []idrefOut{
@@ -438,10 +475,10 @@ func TestIdentityTree(t *testing.T) {
 					chkID.module)
 			}
 
-			if chkID.baseName != "" {
-				if chkID.baseName != foundID.Base.Name {
-					t.Errorf("Couldn't find base %s for ID %s", chkID.baseName,
-						foundID.Base.Name)
+			if len(chkID.baseNames) > 0 {
+				actualBaseNames := getBaseNamesFrom(foundID)
+				if diff := cmp.Diff(actualBaseNames, chkID.baseNames); diff != "" {
+					t.Errorf("(-got, +want):\n%s", diff)
 				}
 			}