Merge "bpdocs for struct types created using reflection" am: 87fd4b11af
am: 8c25febaf9
am: 678e2ea12b
Original change: https://android-review.googlesource.com/c/platform/build/blueprint/+/1820757 Change-Id: Ide155fbc4b2a982177f4082d6a27d05ddc9bd675
This commit is contained in:
commit
10ef8ee70c
4 changed files with 186 additions and 9 deletions
|
@ -121,16 +121,12 @@ func assembleModuleTypeInfo(r *Reader, name string, factory reflect.Value,
|
|||
v := reflect.ValueOf(s).Elem()
|
||||
t := v.Type()
|
||||
|
||||
// Ignore property structs with unexported or unnamed types
|
||||
if t.PkgPath() == "" {
|
||||
continue
|
||||
}
|
||||
ps, err := r.PropertyStruct(t.PkgPath(), t.Name(), v)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ps.ExcludeByTag("blueprint", "mutated")
|
||||
|
||||
for _, nestedProperty := range nestedPropertyStructs(v) {
|
||||
nestedName := nestedProperty.nestPoint
|
||||
nestedValue := nestedProperty.value
|
||||
|
@ -357,7 +353,6 @@ propertyLoop:
|
|||
s := &n[i]
|
||||
if s.SameSubProperties(child) {
|
||||
s.OtherNames = append(s.OtherNames, child.Name)
|
||||
s.OtherTexts = append(s.OtherTexts, child.Text)
|
||||
continue propertyLoop
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,7 +143,26 @@ func (ps *PropertyStruct) GetByName(name string) *Property {
|
|||
}
|
||||
|
||||
func (ps *PropertyStruct) Nest(nested *PropertyStruct) {
|
||||
ps.Properties = append(ps.Properties, nested.Properties...)
|
||||
ps.Properties = nestUnique(ps.Properties, nested.Properties)
|
||||
}
|
||||
|
||||
// Adds a target element to src if it does not exist in src
|
||||
func nestUnique(src []Property, target []Property) []Property {
|
||||
var ret []Property
|
||||
ret = append(ret, src...)
|
||||
for _, elem := range target {
|
||||
isUnique := true
|
||||
for _, retElement := range ret {
|
||||
if elem.Equal(retElement) {
|
||||
isUnique = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if isUnique {
|
||||
ret = append(ret, elem)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getByName(name string, prefix string, props *[]Property) *Property {
|
||||
|
@ -158,7 +177,7 @@ func getByName(name string, prefix string, props *[]Property) *Property {
|
|||
}
|
||||
|
||||
func (p *Property) Nest(nested *PropertyStruct) {
|
||||
p.Properties = append(p.Properties, nested.Properties...)
|
||||
p.Properties = nestUnique(p.Properties, nested.Properties)
|
||||
}
|
||||
|
||||
func (p *Property) SetAnonymous() {
|
||||
|
|
|
@ -16,6 +16,7 @@ package bpdoc
|
|||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -51,6 +52,131 @@ func TestIncludeByTag(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPropertiesOfReflectionStructs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
fields map[string]interface{}
|
||||
expectedProperties map[string]Property
|
||||
description string
|
||||
}{
|
||||
{
|
||||
fields: map[string]interface{}{
|
||||
"A": "A is a string",
|
||||
"B": 0, //B is an int
|
||||
},
|
||||
expectedProperties: map[string]Property{
|
||||
"a": *createProperty("a", "string", ""),
|
||||
"b": *createProperty("b", "int", ""),
|
||||
},
|
||||
description: "struct is composed of primitive types",
|
||||
},
|
||||
{
|
||||
fields: map[string]interface{}{
|
||||
"A": "A is a string",
|
||||
"B": 0, //B is an int
|
||||
"C": props{},
|
||||
},
|
||||
expectedProperties: map[string]Property{
|
||||
"a": *createProperty("a", "string", ""),
|
||||
"b": *createProperty("b", "int", ""),
|
||||
"c": *createProperty("c", "props", "props docs."),
|
||||
},
|
||||
description: "struct is composed of primitive types and other structs",
|
||||
},
|
||||
}
|
||||
|
||||
r := NewReader(pkgFiles)
|
||||
for _, testCase := range testCases {
|
||||
structType := reflectionStructType(testCase.fields)
|
||||
ps, err := r.PropertyStruct(structType.PkgPath(), structType.String(), reflect.New(structType).Elem())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, actualProperty := range ps.Properties {
|
||||
propName := actualProperty.Name
|
||||
assertProperties(t, testCase.expectedProperties[propName], actualProperty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNestUnique(t *testing.T) {
|
||||
testCases := []struct {
|
||||
src []Property
|
||||
target []Property
|
||||
expected []Property
|
||||
description string
|
||||
}{
|
||||
{
|
||||
src: []Property{},
|
||||
target: []Property{},
|
||||
expected: []Property{},
|
||||
description: "Nest Unique fails for empty slice",
|
||||
},
|
||||
{
|
||||
src: []Property{*createProperty("a", "string", ""), *createProperty("b", "string", "")},
|
||||
target: []Property{},
|
||||
expected: []Property{*createProperty("a", "string", ""), *createProperty("b", "string", "")},
|
||||
description: "Nest Unique fails when all elements are unique",
|
||||
},
|
||||
{
|
||||
src: []Property{*createProperty("a", "string", ""), *createProperty("b", "string", "")},
|
||||
target: []Property{*createProperty("c", "string", "")},
|
||||
expected: []Property{*createProperty("a", "string", ""), *createProperty("b", "string", ""), *createProperty("c", "string", "")},
|
||||
description: "Nest Unique fails when all elements are unique",
|
||||
},
|
||||
{
|
||||
src: []Property{*createProperty("a", "string", ""), *createProperty("b", "string", "")},
|
||||
target: []Property{*createProperty("a", "string", "")},
|
||||
expected: []Property{*createProperty("a", "string", ""), *createProperty("b", "string", "")},
|
||||
description: "Nest Unique fails when nested elements are duplicate",
|
||||
},
|
||||
}
|
||||
|
||||
errMsgTemplate := "%s. Expected: %q, Actual: %q"
|
||||
for _, testCase := range testCases {
|
||||
actual := nestUnique(testCase.src, testCase.target)
|
||||
if len(actual) != len(testCase.expected) {
|
||||
t.Errorf(errMsgTemplate, testCase.description, testCase.expected, actual)
|
||||
}
|
||||
for i := 0; i < len(actual); i++ {
|
||||
if !actual[i].Equal(testCase.expected[i]) {
|
||||
t.Errorf(errMsgTemplate, testCase.description, testCase.expected[i], actual[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a struct using reflection and return its type
|
||||
func reflectionStructType(fields map[string]interface{}) reflect.Type {
|
||||
var structFields []reflect.StructField
|
||||
for fieldname, obj := range fields {
|
||||
structField := reflect.StructField{
|
||||
Name: fieldname,
|
||||
Type: reflect.TypeOf(obj),
|
||||
}
|
||||
structFields = append(structFields, structField)
|
||||
}
|
||||
return reflect.StructOf(structFields)
|
||||
}
|
||||
|
||||
// Creates a Property object with a subset of its props populated
|
||||
func createProperty(propName string, propType string, propDocs string) *Property {
|
||||
return &Property{Name: propName, Type: propType, Text: formatText(propDocs)}
|
||||
}
|
||||
|
||||
// Asserts that two Property objects are "similar"
|
||||
// Name, Type and Text properties are checked for similarity
|
||||
func assertProperties(t *testing.T, expected Property, actual Property) {
|
||||
assertStrings(t, expected.Name, actual.Name)
|
||||
assertStrings(t, expected.Type, actual.Type)
|
||||
assertStrings(t, strings.TrimSpace(string(expected.Text)), strings.TrimSpace(string(actual.Text)))
|
||||
}
|
||||
|
||||
func assertStrings(t *testing.T, expected string, actual string) {
|
||||
if expected != actual {
|
||||
t.Errorf("expected: %s, actual: %s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func actualProperties(t *testing.T, props []Property) []string {
|
||||
t.Helper()
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ func (r *Reader) ModuleType(name string, factory reflect.Value) (*ModuleType, er
|
|||
|
||||
// Return the PropertyStruct associated with a property struct type. The type should be in the
|
||||
// format <package path>.<type name>
|
||||
func (r *Reader) PropertyStruct(pkgPath, name string, defaults reflect.Value) (*PropertyStruct, error) {
|
||||
func (r *Reader) propertyStruct(pkgPath, name string, defaults reflect.Value) (*PropertyStruct, error) {
|
||||
ps := r.getPropertyStruct(pkgPath, name)
|
||||
|
||||
if ps == nil {
|
||||
|
@ -113,6 +113,43 @@ func (r *Reader) PropertyStruct(pkgPath, name string, defaults reflect.Value) (*
|
|||
return ps, nil
|
||||
}
|
||||
|
||||
// Return the PropertyStruct associated with a struct type using recursion
|
||||
// This method is useful since golang structs created using reflection have an empty PkgPath()
|
||||
func (r *Reader) PropertyStruct(pkgPath, name string, defaults reflect.Value) (*PropertyStruct, error) {
|
||||
var props []Property
|
||||
|
||||
// Base case: primitive type
|
||||
if defaults.Kind() != reflect.Struct {
|
||||
props = append(props, Property{Name: name,
|
||||
Type: defaults.Type().String()})
|
||||
return &PropertyStruct{Properties: props}, nil
|
||||
}
|
||||
|
||||
// Base case: use r.propertyStruct if struct has a non empty pkgpath
|
||||
if pkgPath != "" {
|
||||
return r.propertyStruct(pkgPath, name, defaults)
|
||||
}
|
||||
|
||||
numFields := defaults.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
field := defaults.Type().Field(i)
|
||||
// Recurse
|
||||
ps, err := r.PropertyStruct(field.Type.PkgPath(), field.Type.Name(), reflect.New(field.Type).Elem())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prop := Property{
|
||||
Name: strings.ToLower(field.Name),
|
||||
Text: formatText(ps.Text),
|
||||
Type: field.Type.Name(),
|
||||
Properties: ps.Properties,
|
||||
}
|
||||
props = append(props, prop)
|
||||
}
|
||||
return &PropertyStruct{Properties: props}, nil
|
||||
}
|
||||
|
||||
func (r *Reader) getModuleTypeDoc(pkgPath, factoryFuncName string) (string, error) {
|
||||
goPkg, err := r.goPkg(pkgPath)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in a new issue