1// Copyright 2019 Google Inc. All rights reserved. 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 15package bpdoc 16 17import ( 18 "reflect" 19 "strings" 20 "testing" 21) 22 23func TestExcludeByTag(t *testing.T) { 24 r := NewReader(pkgFiles) 25 ps, err := r.PropertyStruct(pkgPath, "tagTestProps", reflect.ValueOf(tagTestProps{})) 26 if err != nil { 27 t.Fatal(err) 28 } 29 30 ps.ExcludeByTag("tag1", "a") 31 32 expected := []string{"c", "d", "g"} 33 actual := actualProperties(t, ps.Properties) 34 if !reflect.DeepEqual(expected, actual) { 35 t.Errorf("unexpected ExcludeByTag result, expected: %q, actual: %q", expected, actual) 36 } 37} 38 39func TestIncludeByTag(t *testing.T) { 40 r := NewReader(pkgFiles) 41 ps, err := r.PropertyStruct(pkgPath, "tagTestProps", reflect.ValueOf(tagTestProps{A: "B"})) 42 if err != nil { 43 t.Fatal(err) 44 } 45 46 ps.IncludeByTag("tag1", "c") 47 48 expected := []string{"b", "c", "d", "f", "g"} 49 actual := actualProperties(t, ps.Properties) 50 if !reflect.DeepEqual(expected, actual) { 51 t.Errorf("unexpected IncludeByTag result, expected: %q, actual: %q", expected, actual) 52 } 53} 54 55func TestPropertiesOfReflectionStructs(t *testing.T) { 56 testCases := []struct { 57 fields map[string]interface{} 58 expectedProperties map[string]Property 59 description string 60 }{ 61 { 62 fields: map[string]interface{}{ 63 "A": "A is a string", 64 "B": 0, //B is an int 65 }, 66 expectedProperties: map[string]Property{ 67 "a": *createProperty("a", "string", ""), 68 "b": *createProperty("b", "int", ""), 69 }, 70 description: "struct is composed of primitive types", 71 }, 72 { 73 fields: map[string]interface{}{ 74 "A": "A is a string", 75 "B": 0, //B is an int 76 "C": props{}, 77 }, 78 expectedProperties: map[string]Property{ 79 "a": *createProperty("a", "string", ""), 80 "b": *createProperty("b", "int", ""), 81 "c": *createProperty("c", "props", "props docs."), 82 }, 83 description: "struct is composed of primitive types and other structs", 84 }, 85 } 86 87 r := NewReader(pkgFiles) 88 for _, testCase := range testCases { 89 structType := reflectionStructType(testCase.fields) 90 ps, err := r.PropertyStruct(structType.PkgPath(), structType.String(), reflect.New(structType).Elem()) 91 if err != nil { 92 t.Fatal(err) 93 } 94 for _, actualProperty := range ps.Properties { 95 propName := actualProperty.Name 96 assertProperties(t, testCase.expectedProperties[propName], actualProperty) 97 } 98 } 99} 100 101func TestNestUnique(t *testing.T) { 102 testCases := []struct { 103 src []Property 104 target []Property 105 expected []Property 106 description string 107 }{ 108 { 109 src: []Property{}, 110 target: []Property{}, 111 expected: []Property{}, 112 description: "Nest Unique fails for empty slice", 113 }, 114 { 115 src: []Property{*createProperty("a", "string", ""), *createProperty("b", "string", "")}, 116 target: []Property{}, 117 expected: []Property{*createProperty("a", "string", ""), *createProperty("b", "string", "")}, 118 description: "Nest Unique fails when all elements are unique", 119 }, 120 { 121 src: []Property{*createProperty("a", "string", ""), *createProperty("b", "string", "")}, 122 target: []Property{*createProperty("c", "string", "")}, 123 expected: []Property{*createProperty("a", "string", ""), *createProperty("b", "string", ""), *createProperty("c", "string", "")}, 124 description: "Nest Unique fails when all elements are unique", 125 }, 126 { 127 src: []Property{*createProperty("a", "string", ""), *createProperty("b", "string", "")}, 128 target: []Property{*createProperty("a", "string", "")}, 129 expected: []Property{*createProperty("a", "string", ""), *createProperty("b", "string", "")}, 130 description: "Nest Unique fails when nested elements are duplicate", 131 }, 132 } 133 134 errMsgTemplate := "%s. Expected: %q, Actual: %q" 135 for _, testCase := range testCases { 136 actual := nestUnique(testCase.src, testCase.target) 137 if len(actual) != len(testCase.expected) { 138 t.Errorf(errMsgTemplate, testCase.description, testCase.expected, actual) 139 } 140 for i := 0; i < len(actual); i++ { 141 if !actual[i].Equal(testCase.expected[i]) { 142 t.Errorf(errMsgTemplate, testCase.description, testCase.expected[i], actual[i]) 143 } 144 } 145 } 146} 147 148// Creates a struct using reflection and return its type 149func reflectionStructType(fields map[string]interface{}) reflect.Type { 150 var structFields []reflect.StructField 151 for fieldname, obj := range fields { 152 structField := reflect.StructField{ 153 Name: fieldname, 154 Type: reflect.TypeOf(obj), 155 } 156 structFields = append(structFields, structField) 157 } 158 return reflect.StructOf(structFields) 159} 160 161// Creates a Property object with a subset of its props populated 162func createProperty(propName string, propType string, propDocs string) *Property { 163 return &Property{Name: propName, Type: propType, Text: formatText(propDocs)} 164} 165 166// Asserts that two Property objects are "similar" 167// Name, Type and Text properties are checked for similarity 168func assertProperties(t *testing.T, expected Property, actual Property) { 169 assertStrings(t, expected.Name, actual.Name) 170 assertStrings(t, expected.Type, actual.Type) 171 assertStrings(t, strings.TrimSpace(string(expected.Text)), strings.TrimSpace(string(actual.Text))) 172} 173 174func assertStrings(t *testing.T, expected string, actual string) { 175 if expected != actual { 176 t.Errorf("expected: %s, actual: %s", expected, actual) 177 } 178} 179 180func actualProperties(t *testing.T, props []Property) []string { 181 t.Helper() 182 183 actual := []string{} 184 for _, p := range props { 185 actual = append(actual, p.Name) 186 actual = append(actual, actualProperties(t, p.Properties)...) 187 } 188 return actual 189} 190