From ce92d94ac6235b3852178e44516771f1367f2aa4 Mon Sep 17 00:00:00 2001 From: Rebecca Skinner Date: Thu, 22 Sep 2016 13:36:52 -0500 Subject: [PATCH 1/4] expand handling of anonymous field structs and overlapping names --- render/preprocessor/preprocessor.go | 33 +++- render/preprocessor/preprocessor_test.go | 199 ++++++++++++++++++++++- 2 files changed, 223 insertions(+), 9 deletions(-) diff --git a/render/preprocessor/preprocessor.go b/render/preprocessor/preprocessor.go index 46e8752e6..c5417f9d4 100644 --- a/render/preprocessor/preprocessor.go +++ b/render/preprocessor/preprocessor.go @@ -262,6 +262,7 @@ func EvalTerms(obj interface{}, terms ...string) (interface{}, error) { // a struct. func fieldMap(val interface{}) (map[string]string, error) { fieldMap := make(map[string]string) + conflictMap := make(map[string]struct{}) var t reflect.Type switch typed := val.(type) { case reflect.Type: @@ -277,33 +278,38 @@ func fieldMap(val interface{}) (map[string]string, error) { if t.Kind() != reflect.Struct { return nil, fmt.Errorf("cannot access fields of non-struct type %T", val) } - return addFieldsToMap(fieldMap, t) + return addFieldsToMap(fieldMap, conflictMap, t) } -func addFieldsToMap(m map[string]string, t reflect.Type) (map[string]string, error) { +func addFieldsToMap(m map[string]string, conflicts map[string]struct{}, t reflect.Type) (map[string]string, error) { if cached, ok := fieldMapCache[t]; ok { return cached, nil } - for idx := 0; idx < t.NumField(); idx++ { field := t.Field(idx) if field.Anonymous { + lower := strings.ToLower(field.Name) + if _, ok := m[lower]; !ok { + m[lower] = field.Name + } var err error anonType := interfaceToConcreteType(field.Type) if anonType.Kind() == reflect.Struct { - if m, err = addFieldsToMap(m, anonType); err != nil { + if m, err = addFieldsToMap(m, conflicts, anonType); err != nil { return nil, err } } continue } - name := field.Name lower := strings.ToLower(name) if _, ok := m[lower]; ok { - return nil, fmt.Errorf("multiple potential matches for %s", name) + conflicts[lower] = struct{}{} + } else { + if _, ok := conflicts[lower]; !ok { + m[lower] = name + } } - m[lower] = name } fieldMapCache[t] = m return m, nil @@ -360,3 +366,16 @@ func nilPtrError(v reflect.Value) error { func missingFieldError(name string, v reflect.Value) error { return fmt.Errorf("%s has no field named %s", v.Type().String(), name) } + +func mergeMaps(src, dest map[string]string) map[string]string { + merged := make(map[string]string) + for k, v := range src { + merged[k] = v + } + for k, v := range dest { + if _, ok := merged[k]; !ok { + merged[k] = v + } + } + return merged +} diff --git a/render/preprocessor/preprocessor_test.go b/render/preprocessor/preprocessor_test.go index e5b0573ec..c9954824c 100644 --- a/render/preprocessor/preprocessor_test.go +++ b/render/preprocessor/preprocessor_test.go @@ -206,7 +206,7 @@ func Test_LookupCanonicalFieldName_ReturnsCanonicalFieldName_WhenStructOkay(t *t } -func Test_LookupCanonicalFieldName_ReturnsError_WhenOverlappingFieldNames(t *testing.T) { +func Test_LookupCanonicalFieldName_ReturnsNoError_WhenOverlappingFieldNames(t *testing.T) { type TestStruct struct { Xyz struct{} // collision initial upper XYz struct{} // collision first two upper @@ -214,7 +214,26 @@ func Test_LookupCanonicalFieldName_ReturnsError_WhenOverlappingFieldNames(t *tes testType := reflect.TypeOf(TestStruct{}) _, err := preprocessor.LookupCanonicalFieldName(testType, "xyz") - assert.Error(t, err) + assert.NoError(t, err) +} + +func Test_LookupCanonicalFieldName_ReturnsCorrectNameWhenAnonymousField(t *testing.T) { + type A struct { + Foo string + Bar string + } + type B struct { + A + Foo string + Baz string + } + testType := reflect.TypeOf(B{}) + _, err := preprocessor.LookupCanonicalFieldName(testType, "bar") + assert.NoError(t, err) + _, err = preprocessor.LookupCanonicalFieldName(testType, "baz") + assert.NoError(t, err) + _, err = preprocessor.LookupCanonicalFieldName(testType, "foo") + assert.NoError(t, err) } func Test_EvalTerms(t *testing.T) { @@ -248,6 +267,182 @@ func Test_EvalTerms(t *testing.T) { assert.Equal(t, val, "a") } +func Test_EvalTerms_WhenAnonymousEmbeddedStructs(t *testing.T) { + type A struct { + AOnly string + ACOnly string + ABOnly string + } + type B struct { + A + ABOnly string + BOnly string + BCOnly string + } + type C struct { + A + B + COnly string + BCOnly string + ACOnly string + } + + val := C{ + B: B{ + A: A{ + AOnly: "c.b.a.aonly", + ABOnly: "c.b.a.abonly", + ACOnly: "c.b.a.aconly", + }, + ABOnly: "c.b.abonly", + BOnly: "c.b.bonly", + BCOnly: "c.b.bconly", + }, + A: A{ + AOnly: "c.a.aonly", + ABOnly: "c.a.abonly", + ACOnly: "c.a.aconly", + }, + COnly: "c.conly", + BCOnly: "c.bconly", + ACOnly: "c.aconly", + } + + result, err := preprocessor.EvalTerms(val, "conly") + assert.NoError(t, err) + assert.Equal(t, result, "c.conly") + + result, err = preprocessor.EvalTerms(val, "bconly") + assert.NoError(t, err) + assert.Equal(t, result, "c.bconly") + + result, err = preprocessor.EvalTerms(val, "aconly") + assert.NoError(t, err) + assert.Equal(t, result, "c.aconly") + + _, err = preprocessor.EvalTerms(val, "abonly") + assert.Error(t, err) + + result, err = preprocessor.EvalTerms(val, "b", "abonly") + assert.NoError(t, err) + assert.Equal(t, result, "c.b.abonly") +} + +func Test_EvalTerms_WithAnonymousFields(t *testing.T) { + type A struct { + AField string + } + type B struct { + BField string + } + type C struct { + B + CField string + } + type D struct { + A + C + DField string + } + val := D{ + A: A{ + AField: "afield", + }, + C: C{ + B: B{ + BField: "bfield", + }, + CField: "cfield", + }, + DField: "dfield", + } + + result, err := preprocessor.EvalTerms(val, "dfield") + assert.NoError(t, err) + assert.Equal(t, result, "dfield") + + result, err = preprocessor.EvalTerms(val, "cfield") + assert.NoError(t, err) + assert.Equal(t, result, "cfield") + + result, err = preprocessor.EvalTerms(val, "bfield") + assert.NoError(t, err) + assert.Equal(t, result, "bfield") + + result, err = preprocessor.EvalTerms(val, "afield") + assert.NoError(t, err) + assert.Equal(t, result, "afield") + +} + +func Test_EvalTerms_HandlesOverlappingFieldsNames(t *testing.T) { + type A struct { + Foo string + FooA string + Overlap string + } + type B struct { + Foo string + FooB string + Overlap string + } + type C struct { + A + B + FooC string + Overlap string + } + val := C{ + A: A{Foo: "a.foo", FooA: "a.fooa", Overlap: "a"}, + B: B{Foo: "b.foo", FooB: "b.foob", Overlap: "b"}, + FooC: "c.fooc", + Overlap: "c", + } + + _, err := preprocessor.EvalTerms(val, "foo") + assert.Error(t, err) + + result, err := preprocessor.EvalTerms(val, "fooc") + assert.NoError(t, err) + assert.Equal(t, result, "c.fooc") + + result, err = preprocessor.EvalTerms(val, "fooa") + assert.NoError(t, err) + assert.Equal(t, result, "a.fooa") + + result, err = preprocessor.EvalTerms(val, "a", "fooa") + assert.NoError(t, err) + assert.Equal(t, result, "a.fooa") + + result, err = preprocessor.EvalTerms(val, "foob") + assert.NoError(t, err) + assert.Equal(t, result, "b.foob") + + result, err = preprocessor.EvalTerms(val, "b", "foob") + assert.NoError(t, err) + assert.Equal(t, result, "b.foob") + + result, err = preprocessor.EvalTerms(val, "a", "foo") + assert.NoError(t, err) + assert.Equal(t, result, "a.foo") + + result, err = preprocessor.EvalTerms(val, "b", "foo") + assert.NoError(t, err) + assert.Equal(t, result, "b.foo") + + result, err = preprocessor.EvalTerms(val, "overlap") + assert.NoError(t, err) + assert.Equal(t, result, "c") + + result, err = preprocessor.EvalTerms(val, "a", "overlap") + assert.NoError(t, err) + assert.Equal(t, result, "a") + + result, err = preprocessor.EvalTerms(val, "b", "overlap") + assert.NoError(t, err) + assert.Equal(t, result, "b") +} + type TestStruct struct { FieldA string } From c76c67df8b0b152ee408771d76967cbd018affea Mon Sep 17 00:00:00 2001 From: Rebecca Skinner Date: Thu, 22 Sep 2016 13:39:38 -0500 Subject: [PATCH 2/4] remove extraneous merge maps function --- render/preprocessor/preprocessor.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/render/preprocessor/preprocessor.go b/render/preprocessor/preprocessor.go index c5417f9d4..3b873443a 100644 --- a/render/preprocessor/preprocessor.go +++ b/render/preprocessor/preprocessor.go @@ -366,16 +366,3 @@ func nilPtrError(v reflect.Value) error { func missingFieldError(name string, v reflect.Value) error { return fmt.Errorf("%s has no field named %s", v.Type().String(), name) } - -func mergeMaps(src, dest map[string]string) map[string]string { - merged := make(map[string]string) - for k, v := range src { - merged[k] = v - } - for k, v := range dest { - if _, ok := merged[k]; !ok { - merged[k] = v - } - } - return merged -} From 983fca18548661c0f1233242a98af4106c129f6f Mon Sep 17 00:00:00 2001 From: Rebecca Skinner Date: Thu, 22 Sep 2016 13:46:30 -0500 Subject: [PATCH 3/4] remove underscores as-per codeclimate --- render/preprocessor/preprocessor_test.go | 44 ++++++++++++------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/render/preprocessor/preprocessor_test.go b/render/preprocessor/preprocessor_test.go index c9954824c..a92ac6c28 100644 --- a/render/preprocessor/preprocessor_test.go +++ b/render/preprocessor/preprocessor_test.go @@ -23,17 +23,17 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_Inits_WhenEmptySlice(t *testing.T) { +func TestInitsWhenEmptySlice(t *testing.T) { assert.Nil(t, preprocessor.Inits([]string{})) } -func Test_HasField_WhenStruct_ReturnsFieldPresentWhenPresent(t *testing.T) { +func TestHasFieldWhenStructReturnsFieldPresentWhenPresent(t *testing.T) { assert.True(t, preprocessor.HasField(TestStruct{}, "FieldA")) assert.False(t, preprocessor.HasField(TestStruct{}, "FieldB")) } -func Test_HasField_WhenEmbeddedStruct_ReturnsEmbeddedFieldPresent(t *testing.T) { +func TestHasFieldWhenEmbeddedStructReturnsEmbeddedFieldPresent(t *testing.T) { type Embedded struct { A struct{} } @@ -46,7 +46,7 @@ func Test_HasField_WhenEmbeddedStruct_ReturnsEmbeddedFieldPresent(t *testing.T) assert.True(t, preprocessor.HasField(Embedding{}, "A")) } -func Test_VertexSplit_WhenMatchingSubstring_ReturnsPrefixAndRest(t *testing.T) { +func TestVertexSplitWhenMatchingSubstringReturnsPrefixAndRest(t *testing.T) { s := "a.b.c.d.e" g := graph.New() g.Add("a", "a") @@ -59,7 +59,7 @@ func Test_VertexSplit_WhenMatchingSubstring_ReturnsPrefixAndRest(t *testing.T) { assert.True(t, found) } -func Test_VertexSplit_WhenExactMatch_ReturnsPrefix(t *testing.T) { +func TestVertexSplitWhenExactMatchReturnsPrefix(t *testing.T) { s := "a.b.c" g := graph.New() g.Add("a.b.c", "a.b.c") @@ -69,7 +69,7 @@ func Test_VertexSplit_WhenExactMatch_ReturnsPrefix(t *testing.T) { assert.True(t, found) } -func Test_VertexSplit_WhenNoMatch_ReturnsRest(t *testing.T) { +func TestVertexSplitWhenNoMatchReturnsRest(t *testing.T) { s := "x.y.z" g := graph.New() g.Add("a.b.c", "a.b.c") @@ -79,43 +79,43 @@ func Test_VertexSplit_WhenNoMatch_ReturnsRest(t *testing.T) { assert.False(t, found) } -func Test_HasField_WhenStructPtr_ReturnsFieldPresentWhenPresent(t *testing.T) { +func TestHasFieldWhenStructPtrReturnsFieldPresentWhenPresent(t *testing.T) { assert.True(t, preprocessor.HasField(&TestStruct{}, "FieldA")) assert.False(t, preprocessor.HasField(&TestStruct{}, "FieldB")) } -func Test_HasField_WhenGivenAsLowerCaseAndIsCapital_ReturnsTrue(t *testing.T) { +func TestHasFieldWhenGivenAsLowerCaseAndIsCapitalReturnsTrue(t *testing.T) { assert.True(t, preprocessor.HasField(&TestStruct{}, "fieldA")) assert.True(t, preprocessor.HasField(&TestStruct{}, "fielda")) assert.False(t, preprocessor.HasField(&TestStruct{}, "fieldB")) } -func Test_HasField_WhenNilPtr_ReturnsTrue(t *testing.T) { +func TestHasFieldWhenNilPtrReturnsTrue(t *testing.T) { var test *TestStruct assert.True(t, preprocessor.HasField(test, "FieldA")) assert.False(t, preprocessor.HasField(test, "FieldB")) } -func Test_HasMethod_WhenStruct(t *testing.T) { +func TestHasMethodWhenStruct(t *testing.T) { assert.True(t, preprocessor.HasMethod(TestStruct{}, "FunctionOnStruct")) assert.False(t, preprocessor.HasMethod(TestStruct{}, "FunctionOnPointer")) assert.False(t, preprocessor.HasMethod(TestStruct{}, "NonExistantFunc")) } -func Test_HasMethod_WhenStructPtr(t *testing.T) { +func TestHasMethodWhenStructPtr(t *testing.T) { assert.True(t, preprocessor.HasMethod(&TestStruct{}, "FunctionOnStruct")) assert.True(t, preprocessor.HasMethod(&TestStruct{}, "FunctionOnPointer")) assert.False(t, preprocessor.HasMethod(&TestStruct{}, "NonExistantFunc")) } -func Test_HasMethod_WhenNilPtr_ReturnsFalse(t *testing.T) { +func TestHasMethodWhenNilPtrReturnsFalse(t *testing.T) { var test *TestStruct assert.True(t, preprocessor.HasMethod(test, "FunctionOnStruct")) assert.True(t, preprocessor.HasMethod(test, "FunctionOnPointer")) assert.False(t, preprocessor.HasMethod(test, "NonExistantFunc")) } -func Test_EvalMember_ReturnsValueWhenExists(t *testing.T) { +func TestEvalMemberReturnsValueWhenExists(t *testing.T) { expected := "foo" test := &TestStruct{FieldA: expected} actual, err := preprocessor.EvalMember("FieldA", test) @@ -123,7 +123,7 @@ func Test_EvalMember_ReturnsValueWhenExists(t *testing.T) { assert.Equal(t, expected, actual.Interface().(string)) } -func Test_EvalMember_ReturnsValueWhenLowerCaseAndExists(t *testing.T) { +func TestEvalMemberReturnsValueWhenLowerCaseAndExists(t *testing.T) { expected := "foo" test := &TestStruct{FieldA: expected} actual, err := preprocessor.EvalMember("fieldA", test) @@ -131,13 +131,13 @@ func Test_EvalMember_ReturnsValueWhenLowerCaseAndExists(t *testing.T) { assert.Equal(t, expected, actual.Interface().(string)) } -func Test_EvalMember_ReturnsError_WhenNotExists(t *testing.T) { +func TestEvalMemberReturnsErrorWhenNotExists(t *testing.T) { test := &TestStruct{} _, err := preprocessor.EvalMember("MissingField", test) assert.Error(t, err) } -func Test_LookupCanonicalFieldName_ReturnsCanonicalFieldName_WhenStructOkay(t *testing.T) { +func TestLookupCanonicalFieldNameReturnsCanonicalFieldNameWhenStructOkay(t *testing.T) { type TestStruct struct { ABC struct{} // All upper aaa struct{} // all lower @@ -206,7 +206,7 @@ func Test_LookupCanonicalFieldName_ReturnsCanonicalFieldName_WhenStructOkay(t *t } -func Test_LookupCanonicalFieldName_ReturnsNoError_WhenOverlappingFieldNames(t *testing.T) { +func TestLookupCanonicalFieldNameReturnsNoErrorWhenOverlappingFieldNames(t *testing.T) { type TestStruct struct { Xyz struct{} // collision initial upper XYz struct{} // collision first two upper @@ -217,7 +217,7 @@ func Test_LookupCanonicalFieldName_ReturnsNoError_WhenOverlappingFieldNames(t *t assert.NoError(t, err) } -func Test_LookupCanonicalFieldName_ReturnsCorrectNameWhenAnonymousField(t *testing.T) { +func TestLookupCanonicalFieldNameReturnsCorrectNameWhenAnonymousField(t *testing.T) { type A struct { Foo string Bar string @@ -236,7 +236,7 @@ func Test_LookupCanonicalFieldName_ReturnsCorrectNameWhenAnonymousField(t *testi assert.NoError(t, err) } -func Test_EvalTerms(t *testing.T) { +func TestEvalTerms(t *testing.T) { type C struct { CVal string } @@ -267,7 +267,7 @@ func Test_EvalTerms(t *testing.T) { assert.Equal(t, val, "a") } -func Test_EvalTerms_WhenAnonymousEmbeddedStructs(t *testing.T) { +func TestEvalTermsWhenAnonymousEmbeddedStructs(t *testing.T) { type A struct { AOnly string ACOnly string @@ -328,7 +328,7 @@ func Test_EvalTerms_WhenAnonymousEmbeddedStructs(t *testing.T) { assert.Equal(t, result, "c.b.abonly") } -func Test_EvalTerms_WithAnonymousFields(t *testing.T) { +func TestEvalTermsWithAnonymousFields(t *testing.T) { type A struct { AField string } @@ -375,7 +375,7 @@ func Test_EvalTerms_WithAnonymousFields(t *testing.T) { } -func Test_EvalTerms_HandlesOverlappingFieldsNames(t *testing.T) { +func TestEvalTermsHandlesOverlappingFieldsNames(t *testing.T) { type A struct { Foo string FooA string From 16466c6f60d77a33fc8ac964f70e08ffa756aa60 Mon Sep 17 00:00:00 2001 From: Rebecca Skinner Date: Thu, 22 Sep 2016 14:19:05 -0500 Subject: [PATCH 4/4] refactor tests to use t.Run and t.Parallel --- render/preprocessor/preprocessor_test.go | 833 ++++++++++++----------- 1 file changed, 435 insertions(+), 398 deletions(-) diff --git a/render/preprocessor/preprocessor_test.go b/render/preprocessor/preprocessor_test.go index a92ac6c28..a5c8075b6 100644 --- a/render/preprocessor/preprocessor_test.go +++ b/render/preprocessor/preprocessor_test.go @@ -23,429 +23,466 @@ import ( "github.com/stretchr/testify/assert" ) +// TestInitsWhenEmptySlice ensures that an emptly slice returns an empty slice func TestInitsWhenEmptySlice(t *testing.T) { + t.Parallel() assert.Nil(t, preprocessor.Inits([]string{})) } -func TestHasFieldWhenStructReturnsFieldPresentWhenPresent(t *testing.T) { - assert.True(t, preprocessor.HasField(TestStruct{}, "FieldA")) - assert.False(t, preprocessor.HasField(TestStruct{}, "FieldB")) +// TestHasField ensures the correct behavior when looking for a field on a +// struct +func TestHasField(t *testing.T) { + t.Parallel() + t.Run("TestHasFieldWhenStructReturnsFieldPresentWhenPresent", func(t *testing.T) { + assert.True(t, preprocessor.HasField(TestStruct{}, "FieldA")) + assert.False(t, preprocessor.HasField(TestStruct{}, "FieldB")) + + }) + + t.Run("TestHasFieldWhenEmbeddedStructReturnsEmbeddedFieldPresent", func(t *testing.T) { + type Embedded struct { + A struct{} + } + + type Embedding struct { + *Embedded + B struct{} + } + assert.True(t, preprocessor.HasField(Embedding{}, "B")) + assert.True(t, preprocessor.HasField(Embedding{}, "A")) + }) + + t.Run("TestHasFieldWhenStructPtrReturnsFieldPresentWhenPresent", func(t *testing.T) { + assert.True(t, preprocessor.HasField(&TestStruct{}, "FieldA")) + assert.False(t, preprocessor.HasField(&TestStruct{}, "FieldB")) + }) + + t.Run("TestHasFieldWhenGivenAsLowerCaseAndIsCapitalReturnsTrue", func(t *testing.T) { + assert.True(t, preprocessor.HasField(&TestStruct{}, "fieldA")) + assert.True(t, preprocessor.HasField(&TestStruct{}, "fielda")) + assert.False(t, preprocessor.HasField(&TestStruct{}, "fieldB")) + }) + + t.Run("TestHasFieldWhenNilPtrReturnsTrue", func(t *testing.T) { + var test *TestStruct + assert.True(t, preprocessor.HasField(test, "FieldA")) + assert.False(t, preprocessor.HasField(test, "FieldB")) + }) } -func TestHasFieldWhenEmbeddedStructReturnsEmbeddedFieldPresent(t *testing.T) { - type Embedded struct { - A struct{} - } - - type Embedding struct { - *Embedded - B struct{} - } - assert.True(t, preprocessor.HasField(Embedding{}, "B")) - assert.True(t, preprocessor.HasField(Embedding{}, "A")) -} - -func TestVertexSplitWhenMatchingSubstringReturnsPrefixAndRest(t *testing.T) { - s := "a.b.c.d.e" - g := graph.New() - g.Add("a", "a") - g.Add("a.b", "a.b.") - g.Add("a.c.d.", "a.c.d") - g.Add("a.b.c", "a.b.c") - pfx, rest, found := preprocessor.VertexSplit(g, s) - assert.Equal(t, "a.b.c", pfx) - assert.Equal(t, "d.e", rest) - assert.True(t, found) -} +// TestVertexSplit ensures the correct behavior when stripping off trailing +// fields from a qualified vertex name +func TestVertexSplit(t *testing.T) { + t.Parallel() + t.Run("TestVertexSplitWhenMatchingSubstringReturnsPrefixAndRest", func(t *testing.T) { + s := "a.b.c.d.e" + g := graph.New() + g.Add("a", "a") + g.Add("a.b", "a.b.") + g.Add("a.c.d.", "a.c.d") + g.Add("a.b.c", "a.b.c") + pfx, rest, found := preprocessor.VertexSplit(g, s) + assert.Equal(t, "a.b.c", pfx) + assert.Equal(t, "d.e", rest) + assert.True(t, found) + }) + + t.Run("TestVertexSplitWhenExactMatchReturnsPrefix", func(t *testing.T) { + s := "a.b.c" + g := graph.New() + g.Add("a.b.c", "a.b.c") + pfx, rest, found := preprocessor.VertexSplit(g, s) + assert.Equal(t, "a.b.c", pfx) + assert.Equal(t, "", rest) + assert.True(t, found) + }) + + t.Run("TestVertexSplitWhenNoMatchReturnsRest", func(t *testing.T) { + s := "x.y.z" + g := graph.New() + g.Add("a.b.c", "a.b.c") + pfx, rest, found := preprocessor.VertexSplit(g, s) + assert.Equal(t, "", pfx) + assert.Equal(t, "x.y.z", rest) + assert.False(t, found) + }) -func TestVertexSplitWhenExactMatchReturnsPrefix(t *testing.T) { - s := "a.b.c" - g := graph.New() - g.Add("a.b.c", "a.b.c") - pfx, rest, found := preprocessor.VertexSplit(g, s) - assert.Equal(t, "a.b.c", pfx) - assert.Equal(t, "", rest) - assert.True(t, found) } -func TestVertexSplitWhenNoMatchReturnsRest(t *testing.T) { - s := "x.y.z" - g := graph.New() - g.Add("a.b.c", "a.b.c") - pfx, rest, found := preprocessor.VertexSplit(g, s) - assert.Equal(t, "", pfx) - assert.Equal(t, "x.y.z", rest) - assert.False(t, found) -} - -func TestHasFieldWhenStructPtrReturnsFieldPresentWhenPresent(t *testing.T) { - assert.True(t, preprocessor.HasField(&TestStruct{}, "FieldA")) - assert.False(t, preprocessor.HasField(&TestStruct{}, "FieldB")) -} +// TestHasMethod ensures correct behavior in identifying methods on structs +func TestHasMethod(t *testing.T) { + t.Parallel() + t.Run("TestHasMethodWhenStruct", func(t *testing.T) { + assert.True(t, preprocessor.HasMethod(TestStruct{}, "FunctionOnStruct")) + assert.False(t, preprocessor.HasMethod(TestStruct{}, "FunctionOnPointer")) + assert.False(t, preprocessor.HasMethod(TestStruct{}, "NonExistantFunc")) + }) + + t.Run("TestHasMethodWhenStructPtr", func(t *testing.T) { + assert.True(t, preprocessor.HasMethod(&TestStruct{}, "FunctionOnStruct")) + assert.True(t, preprocessor.HasMethod(&TestStruct{}, "FunctionOnPointer")) + assert.False(t, preprocessor.HasMethod(&TestStruct{}, "NonExistantFunc")) + }) + + t.Run("TestHasMethodWhenNilPtrReturnsFalse", func(t *testing.T) { + var test *TestStruct + assert.True(t, preprocessor.HasMethod(test, "FunctionOnStruct")) + assert.True(t, preprocessor.HasMethod(test, "FunctionOnPointer")) + assert.False(t, preprocessor.HasMethod(test, "NonExistantFunc")) + }) -func TestHasFieldWhenGivenAsLowerCaseAndIsCapitalReturnsTrue(t *testing.T) { - assert.True(t, preprocessor.HasField(&TestStruct{}, "fieldA")) - assert.True(t, preprocessor.HasField(&TestStruct{}, "fielda")) - assert.False(t, preprocessor.HasField(&TestStruct{}, "fieldB")) } -func TestHasFieldWhenNilPtrReturnsTrue(t *testing.T) { - var test *TestStruct - assert.True(t, preprocessor.HasField(test, "FieldA")) - assert.False(t, preprocessor.HasField(test, "FieldB")) +// TestEvalMember ensures correct behavior when extracting a field from a struct +func TestEvalMember(t *testing.T) { + t.Parallel() + t.Run("TestEvalMemberReturnsValueWhenExists", func(t *testing.T) { + expected := "foo" + test := &TestStruct{FieldA: expected} + actual, err := preprocessor.EvalMember("FieldA", test) + assert.NoError(t, err) + assert.Equal(t, expected, actual.Interface().(string)) + }) + + t.Run("TestEvalMemberReturnsValueWhenLowerCaseAndExists", func(t *testing.T) { + expected := "foo" + test := &TestStruct{FieldA: expected} + actual, err := preprocessor.EvalMember("fieldA", test) + assert.NoError(t, err) + assert.Equal(t, expected, actual.Interface().(string)) + }) + + t.Run("TestEvalMemberReturnsErrorWhenNotExists", func(t *testing.T) { + test := &TestStruct{} + _, err := preprocessor.EvalMember("MissingField", test) + assert.Error(t, err) + }) } -func TestHasMethodWhenStruct(t *testing.T) { - assert.True(t, preprocessor.HasMethod(TestStruct{}, "FunctionOnStruct")) - assert.False(t, preprocessor.HasMethod(TestStruct{}, "FunctionOnPointer")) - assert.False(t, preprocessor.HasMethod(TestStruct{}, "NonExistantFunc")) -} - -func TestHasMethodWhenStructPtr(t *testing.T) { - assert.True(t, preprocessor.HasMethod(&TestStruct{}, "FunctionOnStruct")) - assert.True(t, preprocessor.HasMethod(&TestStruct{}, "FunctionOnPointer")) - assert.False(t, preprocessor.HasMethod(&TestStruct{}, "NonExistantFunc")) -} - -func TestHasMethodWhenNilPtrReturnsFalse(t *testing.T) { - var test *TestStruct - assert.True(t, preprocessor.HasMethod(test, "FunctionOnStruct")) - assert.True(t, preprocessor.HasMethod(test, "FunctionOnPointer")) - assert.False(t, preprocessor.HasMethod(test, "NonExistantFunc")) -} - -func TestEvalMemberReturnsValueWhenExists(t *testing.T) { - expected := "foo" - test := &TestStruct{FieldA: expected} - actual, err := preprocessor.EvalMember("FieldA", test) - assert.NoError(t, err) - assert.Equal(t, expected, actual.Interface().(string)) -} - -func TestEvalMemberReturnsValueWhenLowerCaseAndExists(t *testing.T) { - expected := "foo" - test := &TestStruct{FieldA: expected} - actual, err := preprocessor.EvalMember("fieldA", test) - assert.NoError(t, err) - assert.Equal(t, expected, actual.Interface().(string)) -} - -func TestEvalMemberReturnsErrorWhenNotExists(t *testing.T) { - test := &TestStruct{} - _, err := preprocessor.EvalMember("MissingField", test) - assert.Error(t, err) -} - -func TestLookupCanonicalFieldNameReturnsCanonicalFieldNameWhenStructOkay(t *testing.T) { - type TestStruct struct { - ABC struct{} // All upper - aaa struct{} // all lower - - AcB struct{} // mixed case, initial capital - bAc struct{} // mixed case, initial lower - - A struct{} // single letter upper - b struct{} // single letter lower - } - testType := reflect.TypeOf(TestStruct{}) - - actual, err := preprocessor.LookupCanonicalFieldName(testType, "ABC") - assert.NoError(t, err) - assert.Equal(t, "ABC", actual) - actual, err = preprocessor.LookupCanonicalFieldName(testType, "AbC") - assert.NoError(t, err) - assert.Equal(t, "ABC", actual) - actual, err = preprocessor.LookupCanonicalFieldName(testType, "abc") - assert.NoError(t, err) - assert.Equal(t, "ABC", actual) - - actual, err = preprocessor.LookupCanonicalFieldName(testType, "aaa") - assert.NoError(t, err) - assert.Equal(t, "aaa", actual) - actual, err = preprocessor.LookupCanonicalFieldName(testType, "aAa") - assert.NoError(t, err) - assert.Equal(t, "aaa", actual) - actual, err = preprocessor.LookupCanonicalFieldName(testType, "AAA") - assert.NoError(t, err) - assert.Equal(t, "aaa", actual) - - actual, err = preprocessor.LookupCanonicalFieldName(testType, "acb") - assert.NoError(t, err) - assert.Equal(t, "AcB", actual) - actual, err = preprocessor.LookupCanonicalFieldName(testType, "AcB") - assert.NoError(t, err) - assert.Equal(t, "AcB", actual) - actual, err = preprocessor.LookupCanonicalFieldName(testType, "aCb") - assert.NoError(t, err) - assert.Equal(t, "AcB", actual) - - actual, err = preprocessor.LookupCanonicalFieldName(testType, "bac") - assert.NoError(t, err) - assert.Equal(t, "bAc", actual) - actual, err = preprocessor.LookupCanonicalFieldName(testType, "BaC") - assert.NoError(t, err) - assert.Equal(t, "bAc", actual) - actual, err = preprocessor.LookupCanonicalFieldName(testType, "BAC") - assert.NoError(t, err) - assert.Equal(t, "bAc", actual) - - actual, err = preprocessor.LookupCanonicalFieldName(testType, "A") - assert.NoError(t, err) - assert.Equal(t, "A", actual) - actual, err = preprocessor.LookupCanonicalFieldName(testType, "a") - assert.NoError(t, err) - assert.Equal(t, "A", actual) - - actual, err = preprocessor.LookupCanonicalFieldName(testType, "b") - assert.NoError(t, err) - assert.Equal(t, "b", actual) - actual, err = preprocessor.LookupCanonicalFieldName(testType, "B") - assert.NoError(t, err) - assert.Equal(t, "b", actual) - -} - -func TestLookupCanonicalFieldNameReturnsNoErrorWhenOverlappingFieldNames(t *testing.T) { - type TestStruct struct { - Xyz struct{} // collision initial upper - XYz struct{} // collision first two upper - } - - testType := reflect.TypeOf(TestStruct{}) - _, err := preprocessor.LookupCanonicalFieldName(testType, "xyz") - assert.NoError(t, err) -} - -func TestLookupCanonicalFieldNameReturnsCorrectNameWhenAnonymousField(t *testing.T) { - type A struct { - Foo string - Bar string - } - type B struct { - A - Foo string - Baz string - } - testType := reflect.TypeOf(B{}) - _, err := preprocessor.LookupCanonicalFieldName(testType, "bar") - assert.NoError(t, err) - _, err = preprocessor.LookupCanonicalFieldName(testType, "baz") - assert.NoError(t, err) - _, err = preprocessor.LookupCanonicalFieldName(testType, "foo") - assert.NoError(t, err) +// TestLookupCanonicalFieldName ensures the correct behavior when normalizing a +// field name and accessing it through struct named fields, and named anonymous +// embedded structs. +func TestLookupCanonicalFieldName(t *testing.T) { + t.Parallel() + t.Run("TestLookupCanonicalFieldNameReturnsCanonicalFieldNameWhenStructOkay", func(t *testing.T) { + type TestStruct struct { + ABC struct{} // All upper + aaa struct{} // all lower + + AcB struct{} // mixed case, initial capital + bAc struct{} // mixed case, initial lower + + A struct{} // single letter upper + b struct{} // single letter lower + } + testType := reflect.TypeOf(TestStruct{}) + + actual, err := preprocessor.LookupCanonicalFieldName(testType, "ABC") + assert.NoError(t, err) + assert.Equal(t, "ABC", actual) + actual, err = preprocessor.LookupCanonicalFieldName(testType, "AbC") + assert.NoError(t, err) + assert.Equal(t, "ABC", actual) + actual, err = preprocessor.LookupCanonicalFieldName(testType, "abc") + assert.NoError(t, err) + assert.Equal(t, "ABC", actual) + + actual, err = preprocessor.LookupCanonicalFieldName(testType, "aaa") + assert.NoError(t, err) + assert.Equal(t, "aaa", actual) + actual, err = preprocessor.LookupCanonicalFieldName(testType, "aAa") + assert.NoError(t, err) + assert.Equal(t, "aaa", actual) + actual, err = preprocessor.LookupCanonicalFieldName(testType, "AAA") + assert.NoError(t, err) + assert.Equal(t, "aaa", actual) + + actual, err = preprocessor.LookupCanonicalFieldName(testType, "acb") + assert.NoError(t, err) + assert.Equal(t, "AcB", actual) + actual, err = preprocessor.LookupCanonicalFieldName(testType, "AcB") + assert.NoError(t, err) + assert.Equal(t, "AcB", actual) + actual, err = preprocessor.LookupCanonicalFieldName(testType, "aCb") + assert.NoError(t, err) + assert.Equal(t, "AcB", actual) + + actual, err = preprocessor.LookupCanonicalFieldName(testType, "bac") + assert.NoError(t, err) + assert.Equal(t, "bAc", actual) + actual, err = preprocessor.LookupCanonicalFieldName(testType, "BaC") + assert.NoError(t, err) + assert.Equal(t, "bAc", actual) + actual, err = preprocessor.LookupCanonicalFieldName(testType, "BAC") + assert.NoError(t, err) + assert.Equal(t, "bAc", actual) + + actual, err = preprocessor.LookupCanonicalFieldName(testType, "A") + assert.NoError(t, err) + assert.Equal(t, "A", actual) + actual, err = preprocessor.LookupCanonicalFieldName(testType, "a") + assert.NoError(t, err) + assert.Equal(t, "A", actual) + + actual, err = preprocessor.LookupCanonicalFieldName(testType, "b") + assert.NoError(t, err) + assert.Equal(t, "b", actual) + actual, err = preprocessor.LookupCanonicalFieldName(testType, "B") + assert.NoError(t, err) + assert.Equal(t, "b", actual) + + }) + + t.Run("TestLookupCanonicalFieldNameReturnsNoErrorWhenOverlappingFieldNames", func(t *testing.T) { + type TestStruct struct { + Xyz struct{} // collision initial upper + XYz struct{} // collision first two upper + } + + testType := reflect.TypeOf(TestStruct{}) + _, err := preprocessor.LookupCanonicalFieldName(testType, "xyz") + assert.NoError(t, err) + }) + + t.Run("TestLookupCanonicalFieldNameReturnsCorrectNameWhenAnonymousField", func(t *testing.T) { + type A struct { + Foo string + Bar string + } + type B struct { + A + Foo string + Baz string + } + testType := reflect.TypeOf(B{}) + _, err := preprocessor.LookupCanonicalFieldName(testType, "bar") + assert.NoError(t, err) + _, err = preprocessor.LookupCanonicalFieldName(testType, "baz") + assert.NoError(t, err) + _, err = preprocessor.LookupCanonicalFieldName(testType, "foo") + assert.NoError(t, err) + }) } +// TestEvalTerms tests pulling field values from a struct in different scenarios func TestEvalTerms(t *testing.T) { - type C struct { - CVal string - } - type B struct { - BVal string - BC *C - } - - type A struct { - AVal string - AB *B - } - a := &A{AVal: "a", AB: &B{BVal: "b", BC: &C{CVal: "c"}}} - val, err := preprocessor.EvalTerms(a, "AB", "BVal") - assert.NoError(t, err) - assert.Equal(t, val, "b") - - val, err = preprocessor.EvalTerms(a, "AB", "BC", "CVal") - assert.NoError(t, err) - assert.Equal(t, val, "c") - - val, err = preprocessor.EvalTerms(a, "ab", "bc", "cval") - assert.NoError(t, err) - assert.Equal(t, val, "c") - - val, err = preprocessor.EvalTerms(a, "AVal") - assert.NoError(t, err) - assert.Equal(t, val, "a") -} - -func TestEvalTermsWhenAnonymousEmbeddedStructs(t *testing.T) { - type A struct { - AOnly string - ACOnly string - ABOnly string - } - type B struct { - A - ABOnly string - BOnly string - BCOnly string - } - type C struct { - A - B - COnly string - BCOnly string - ACOnly string - } - - val := C{ - B: B{ + t.Parallel() + t.Run("well-formed", func(t *testing.T) { + type C struct { + CVal string + } + type B struct { + BVal string + BC *C + } + + type A struct { + AVal string + AB *B + } + a := &A{AVal: "a", AB: &B{BVal: "b", BC: &C{CVal: "c"}}} + val, err := preprocessor.EvalTerms(a, "AB", "BVal") + assert.NoError(t, err) + assert.Equal(t, val, "b") + + val, err = preprocessor.EvalTerms(a, "AB", "BC", "CVal") + assert.NoError(t, err) + assert.Equal(t, val, "c") + + val, err = preprocessor.EvalTerms(a, "ab", "bc", "cval") + assert.NoError(t, err) + assert.Equal(t, val, "c") + + val, err = preprocessor.EvalTerms(a, "AVal") + assert.NoError(t, err) + assert.Equal(t, val, "a") + }) + + t.Run("non-overlapping-anonymous", func(t *testing.T) { + type A struct { + AOnly string + ACOnly string + ABOnly string + } + type B struct { + A + ABOnly string + BOnly string + BCOnly string + } + type C struct { + A + B + COnly string + BCOnly string + ACOnly string + } + + val := C{ + B: B{ + A: A{ + AOnly: "c.b.a.aonly", + ABOnly: "c.b.a.abonly", + ACOnly: "c.b.a.aconly", + }, + ABOnly: "c.b.abonly", + BOnly: "c.b.bonly", + BCOnly: "c.b.bconly", + }, A: A{ - AOnly: "c.b.a.aonly", - ABOnly: "c.b.a.abonly", - ACOnly: "c.b.a.aconly", + AOnly: "c.a.aonly", + ABOnly: "c.a.abonly", + ACOnly: "c.a.aconly", }, - ABOnly: "c.b.abonly", - BOnly: "c.b.bonly", - BCOnly: "c.b.bconly", - }, - A: A{ - AOnly: "c.a.aonly", - ABOnly: "c.a.abonly", - ACOnly: "c.a.aconly", - }, - COnly: "c.conly", - BCOnly: "c.bconly", - ACOnly: "c.aconly", - } - - result, err := preprocessor.EvalTerms(val, "conly") - assert.NoError(t, err) - assert.Equal(t, result, "c.conly") - - result, err = preprocessor.EvalTerms(val, "bconly") - assert.NoError(t, err) - assert.Equal(t, result, "c.bconly") - - result, err = preprocessor.EvalTerms(val, "aconly") - assert.NoError(t, err) - assert.Equal(t, result, "c.aconly") - - _, err = preprocessor.EvalTerms(val, "abonly") - assert.Error(t, err) - - result, err = preprocessor.EvalTerms(val, "b", "abonly") - assert.NoError(t, err) - assert.Equal(t, result, "c.b.abonly") -} - -func TestEvalTermsWithAnonymousFields(t *testing.T) { - type A struct { - AField string - } - type B struct { - BField string - } - type C struct { - B - CField string - } - type D struct { - A - C - DField string - } - val := D{ - A: A{ - AField: "afield", - }, - C: C{ - B: B{ - BField: "bfield", + COnly: "c.conly", + BCOnly: "c.bconly", + ACOnly: "c.aconly", + } + + result, err := preprocessor.EvalTerms(val, "conly") + assert.NoError(t, err) + assert.Equal(t, result, "c.conly") + + result, err = preprocessor.EvalTerms(val, "bconly") + assert.NoError(t, err) + assert.Equal(t, result, "c.bconly") + + result, err = preprocessor.EvalTerms(val, "aconly") + assert.NoError(t, err) + assert.Equal(t, result, "c.aconly") + + _, err = preprocessor.EvalTerms(val, "abonly") + assert.Error(t, err) + + result, err = preprocessor.EvalTerms(val, "b", "abonly") + assert.NoError(t, err) + assert.Equal(t, result, "c.b.abonly") + }) + + t.Run("nested-anonymous-no-overlap", func(t *testing.T) { + type A struct { + AField string + } + type B struct { + BField string + } + type C struct { + B + CField string + } + type D struct { + A + C + DField string + } + val := D{ + A: A{ + AField: "afield", }, - CField: "cfield", - }, - DField: "dfield", - } - - result, err := preprocessor.EvalTerms(val, "dfield") - assert.NoError(t, err) - assert.Equal(t, result, "dfield") - - result, err = preprocessor.EvalTerms(val, "cfield") - assert.NoError(t, err) - assert.Equal(t, result, "cfield") - - result, err = preprocessor.EvalTerms(val, "bfield") - assert.NoError(t, err) - assert.Equal(t, result, "bfield") - - result, err = preprocessor.EvalTerms(val, "afield") - assert.NoError(t, err) - assert.Equal(t, result, "afield") - -} - -func TestEvalTermsHandlesOverlappingFieldsNames(t *testing.T) { - type A struct { - Foo string - FooA string - Overlap string - } - type B struct { - Foo string - FooB string - Overlap string - } - type C struct { - A - B - FooC string - Overlap string - } - val := C{ - A: A{Foo: "a.foo", FooA: "a.fooa", Overlap: "a"}, - B: B{Foo: "b.foo", FooB: "b.foob", Overlap: "b"}, - FooC: "c.fooc", - Overlap: "c", - } - - _, err := preprocessor.EvalTerms(val, "foo") - assert.Error(t, err) - - result, err := preprocessor.EvalTerms(val, "fooc") - assert.NoError(t, err) - assert.Equal(t, result, "c.fooc") - - result, err = preprocessor.EvalTerms(val, "fooa") - assert.NoError(t, err) - assert.Equal(t, result, "a.fooa") - - result, err = preprocessor.EvalTerms(val, "a", "fooa") - assert.NoError(t, err) - assert.Equal(t, result, "a.fooa") - - result, err = preprocessor.EvalTerms(val, "foob") - assert.NoError(t, err) - assert.Equal(t, result, "b.foob") - - result, err = preprocessor.EvalTerms(val, "b", "foob") - assert.NoError(t, err) - assert.Equal(t, result, "b.foob") - - result, err = preprocessor.EvalTerms(val, "a", "foo") - assert.NoError(t, err) - assert.Equal(t, result, "a.foo") - - result, err = preprocessor.EvalTerms(val, "b", "foo") - assert.NoError(t, err) - assert.Equal(t, result, "b.foo") - - result, err = preprocessor.EvalTerms(val, "overlap") - assert.NoError(t, err) - assert.Equal(t, result, "c") - - result, err = preprocessor.EvalTerms(val, "a", "overlap") - assert.NoError(t, err) - assert.Equal(t, result, "a") - - result, err = preprocessor.EvalTerms(val, "b", "overlap") - assert.NoError(t, err) - assert.Equal(t, result, "b") + C: C{ + B: B{ + BField: "bfield", + }, + CField: "cfield", + }, + DField: "dfield", + } + + result, err := preprocessor.EvalTerms(val, "dfield") + assert.NoError(t, err) + assert.Equal(t, result, "dfield") + + result, err = preprocessor.EvalTerms(val, "cfield") + assert.NoError(t, err) + assert.Equal(t, result, "cfield") + + result, err = preprocessor.EvalTerms(val, "bfield") + assert.NoError(t, err) + assert.Equal(t, result, "bfield") + + result, err = preprocessor.EvalTerms(val, "afield") + assert.NoError(t, err) + assert.Equal(t, result, "afield") + + }) + + t.Run("anonymous-overlapping", func(t *testing.T) { + type A struct { + Foo string + FooA string + Overlap string + } + type B struct { + Foo string + FooB string + Overlap string + } + type C struct { + B + FooC string + Overlap string + A + } + val := C{ + A: A{Foo: "a.foo", FooA: "a.fooa", Overlap: "a"}, + B: B{Foo: "b.foo", FooB: "b.foob", Overlap: "b"}, + FooC: "c.fooc", + Overlap: "c", + } + + _, err := preprocessor.EvalTerms(val, "foo") + assert.Error(t, err) + + result, err := preprocessor.EvalTerms(val, "fooc") + assert.NoError(t, err) + assert.Equal(t, result, "c.fooc") + + result, err = preprocessor.EvalTerms(val, "fooa") + assert.NoError(t, err) + assert.Equal(t, result, "a.fooa") + + result, err = preprocessor.EvalTerms(val, "a", "fooa") + assert.NoError(t, err) + assert.Equal(t, result, "a.fooa") + + result, err = preprocessor.EvalTerms(val, "foob") + assert.NoError(t, err) + assert.Equal(t, result, "b.foob") + + result, err = preprocessor.EvalTerms(val, "b", "foob") + assert.NoError(t, err) + assert.Equal(t, result, "b.foob") + + result, err = preprocessor.EvalTerms(val, "a", "foo") + assert.NoError(t, err) + assert.Equal(t, result, "a.foo") + + result, err = preprocessor.EvalTerms(val, "b", "foo") + assert.NoError(t, err) + assert.Equal(t, result, "b.foo") + + result, err = preprocessor.EvalTerms(val, "overlap") + assert.NoError(t, err) + assert.Equal(t, result, "c") + + result, err = preprocessor.EvalTerms(val, "a", "overlap") + assert.NoError(t, err) + assert.Equal(t, result, "a") + + result, err = preprocessor.EvalTerms(val, "b", "overlap") + assert.NoError(t, err) + assert.Equal(t, result, "b") + }) } +// TestStruct is a stub object type TestStruct struct { FieldA string } -func (t TestStruct) FunctionOnStruct() {} +// FunctionOnStruct is a stub function +func (t TestStruct) FunctionOnStruct() {} + +// FunctionOnPointer is a stub functino func (t *TestStruct) FunctionOnPointer() {}