diff --git a/Blueprints b/Blueprints index 54ea682..84345a3 100644 --- a/Blueprints +++ b/Blueprints @@ -68,10 +68,12 @@ bootstrap_go_package( "proptools/clone.go", "proptools/extend.go", "proptools/proptools.go", + "proptools/typeequal.go", ], testSrcs = [ "proptools/clone_test.go", "proptools/extend_test.go", + "proptools/typeequal_test.go", ], ) diff --git a/build.ninja.in b/build.ninja.in index d205001..f165c40 100644 --- a/build.ninja.in +++ b/build.ninja.in @@ -81,7 +81,7 @@ default $ # Variant: # Type: bootstrap_go_package # Factory: github.com/google/blueprint/bootstrap.func·003 -# Defined: Blueprints:78:1 +# Defined: Blueprints:80:1 build $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a $ @@ -108,7 +108,7 @@ default $ # Variant: # Type: bootstrap_go_package # Factory: github.com/google/blueprint/bootstrap.func·003 -# Defined: Blueprints:97:1 +# Defined: Blueprints:99:1 build $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $ @@ -181,7 +181,8 @@ build $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $ : g.bootstrap.compile ${g.bootstrap.srcDir}/proptools/clone.go $ ${g.bootstrap.srcDir}/proptools/extend.go $ - ${g.bootstrap.srcDir}/proptools/proptools.go | $ + ${g.bootstrap.srcDir}/proptools/proptools.go $ + ${g.bootstrap.srcDir}/proptools/typeequal.go | $ ${g.bootstrap.compileCmd} pkgPath = github.com/google/blueprint/proptools default $ @@ -192,7 +193,7 @@ default $ # Variant: # Type: bootstrap_core_go_binary # Factory: github.com/google/blueprint/bootstrap.func·005 -# Defined: Blueprints:140:1 +# Defined: Blueprints:142:1 build ${g.bootstrap.buildDir}/.bootstrap/choosestage/obj/choosestage.a: $ g.bootstrap.compile ${g.bootstrap.srcDir}/choosestage/choosestage.go | $ @@ -214,7 +215,7 @@ default ${g.bootstrap.BinDir}/choosestage # Variant: # Type: bootstrap_core_go_binary # Factory: github.com/google/blueprint/bootstrap.func·005 -# Defined: Blueprints:130:1 +# Defined: Blueprints:132:1 build ${g.bootstrap.buildDir}/.bootstrap/gotestmain/obj/gotestmain.a: $ g.bootstrap.compile ${g.bootstrap.srcDir}/gotestmain/gotestmain.go | $ @@ -236,7 +237,7 @@ default ${g.bootstrap.BinDir}/gotestmain # Variant: # Type: bootstrap_core_go_binary # Factory: github.com/google/blueprint/bootstrap.func·005 -# Defined: Blueprints:135:1 +# Defined: Blueprints:137:1 build ${g.bootstrap.buildDir}/.bootstrap/gotestrunner/obj/gotestrunner.a: $ g.bootstrap.compile ${g.bootstrap.srcDir}/gotestrunner/gotestrunner.go $ @@ -258,7 +259,7 @@ default ${g.bootstrap.BinDir}/gotestrunner # Variant: # Type: bootstrap_core_go_binary # Factory: github.com/google/blueprint/bootstrap.func·005 -# Defined: Blueprints:109:1 +# Defined: Blueprints:111:1 build ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/minibp.a: $ g.bootstrap.compile ${g.bootstrap.srcDir}/bootstrap/minibp/main.go | $ diff --git a/proptools/typeequal.go b/proptools/typeequal.go new file mode 100644 index 0000000..e68f91a --- /dev/null +++ b/proptools/typeequal.go @@ -0,0 +1,76 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proptools + +import "reflect" + +// TypeEqual takes two property structs, and returns true if they are of equal type, any embedded +// pointers to structs or interfaces having matching nilitude, and any interface{} values in any +// embedded structs, pointers to structs, or interfaces are also of equal type. +func TypeEqual(s1, s2 interface{}) bool { + return typeEqual(reflect.ValueOf(s1), reflect.ValueOf(s2)) +} + +func typeEqual(v1, v2 reflect.Value) bool { + if v1.Type() != v2.Type() { + return false + } + + if v1.Kind() == reflect.Interface { + if v1.IsNil() != v2.IsNil() { + return false + } + if v1.IsNil() { + return true + } + v1 = v1.Elem() + v2 = v2.Elem() + if v1.Type() != v2.Type() { + return false + } + } + + if v1.Kind() == reflect.Ptr { + if v1.Type().Elem().Kind() != reflect.Struct { + return true + } + if v1.IsNil() != v2.IsNil() { + return false + } + if v1.IsNil() { + return true + } + v1 = v1.Elem() + v2 = v2.Elem() + } + + if v1.Kind() != reflect.Struct { + return true + } + + for i := 0; i < v1.NumField(); i++ { + v1 := v1.Field(i) + v2 := v2.Field(i) + + switch kind := v1.Kind(); kind { + case reflect.Interface, reflect.Ptr, reflect.Struct: + if !typeEqual(v1, v2) { + return false + } + } + } + + return true +} diff --git a/proptools/typeequal_test.go b/proptools/typeequal_test.go new file mode 100644 index 0000000..d374609 --- /dev/null +++ b/proptools/typeequal_test.go @@ -0,0 +1,150 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proptools + +import ( + "fmt" + "testing" +) + +var typeEqualTestCases = []struct { + in1 interface{} + in2 interface{} + out bool +}{ + { + // Matching structs + in1: struct{ S1 string }{}, + in2: struct{ S1 string }{}, + out: true, + }, + { + // Mismatching structs + in1: struct{ S1 string }{}, + in2: struct{ S2 string }{}, + out: false, + }, + { + // Matching pointer to struct + in1: &struct{ S1 string }{}, + in2: &struct{ S1 string }{}, + out: true, + }, + { + // Mismatching pointer to struct + in1: &struct{ S1 string }{}, + in2: &struct{ S2 string }{}, + out: false, + }, + { + // Matching embedded structs + in1: struct{ S struct{ S1 string } }{}, + in2: struct{ S struct{ S1 string } }{}, + out: true, + }, + { + // Misatching embedded structs + in1: struct{ S struct{ S1 string } }{}, + in2: struct{ S struct{ S2 string } }{}, + out: false, + }, + { + // Matching embedded pointer to struct + in1: &struct{ S *struct{ S1 string } }{S: &struct{ S1 string }{}}, + in2: &struct{ S *struct{ S1 string } }{S: &struct{ S1 string }{}}, + out: true, + }, + { + // Mismatching embedded pointer to struct + in1: &struct{ S *struct{ S1 string } }{S: &struct{ S1 string }{}}, + in2: &struct{ S *struct{ S2 string } }{S: &struct{ S2 string }{}}, + out: false, + }, + { + // Matching embedded nil pointer to struct + in1: &struct{ S *struct{ S1 string } }{}, + in2: &struct{ S *struct{ S1 string } }{}, + out: true, + }, + { + // Mismatching embedded nil pointer to struct + in1: &struct{ S *struct{ S1 string } }{}, + in2: &struct{ S *struct{ S2 string } }{}, + out: false, + }, + { + // Mismatching nilitude embedded pointer to struct + in1: &struct{ S *struct{ S1 string } }{S: &struct{ S1 string }{}}, + in2: &struct{ S *struct{ S1 string } }{}, + out: false, + }, + { + // Matching embedded interface to pointer to struct + in1: &struct{ S interface{} }{S: &struct{ S1 string }{}}, + in2: &struct{ S interface{} }{S: &struct{ S1 string }{}}, + out: true, + }, + { + // Mismatching embedded interface to pointer to struct + in1: &struct{ S interface{} }{S: &struct{ S1 string }{}}, + in2: &struct{ S interface{} }{S: &struct{ S2 string }{}}, + out: false, + }, + { + // Matching embedded nil interface to pointer to struct + in1: &struct{ S interface{} }{}, + in2: &struct{ S interface{} }{}, + out: true, + }, + { + // Mismatching nilitude embedded interface to pointer to struct + in1: &struct{ S interface{} }{S: &struct{ S1 string }{}}, + in2: &struct{ S interface{} }{}, + out: false, + }, + { + // Matching pointer to non-struct + in1: struct{ S1 *string }{ S1: StringPtr("test1") }, + in2: struct{ S1 *string }{ S1: StringPtr("test2") }, + out: true, + }, + { + // Matching nilitude pointer to non-struct + in1: struct{ S1 *string }{ S1: StringPtr("test1") }, + in2: struct{ S1 *string }{}, + out: true, + }, + { + // Mismatching pointer to non-struct + in1: struct{ S1 *string }{}, + in2: struct{ S2 *string }{}, + out: false, + }, +} + +func TestTypeEqualProperties(t *testing.T) { + for _, testCase := range typeEqualTestCases { + testString := fmt.Sprintf("%#v, %#v -> %t", testCase.in1, testCase.in2, testCase.out) + + got := TypeEqual(testCase.in1, testCase.in2) + + if got != testCase.out { + t.Errorf("test case: %s", testString) + t.Errorf("incorrect output") + t.Errorf(" expected: %t", testCase.out) + t.Errorf(" got: %t", got) + } + } +}