1109cd96b7
struct types defined using reflection have empty PkgPaths(), due to which they were not added to the generated bpdocs. This CL allows us to generate bpdocs for such struct types. For each field in the struct, the algorithm checks the following and creates a PropertyStruct object accordingly 1. If field is a primitive type (Base condition) 2. If field is another exported struct (Base condition) 3. If field is another struct created using reflection (Recurse) Test: m soong_docs Test: java_sdk_library_import before https://ci.android.com/builds/submitted/7710820/linux/latest/view/java.html#java_sdk_library_import and after https://spandandas.users.x20web.corp.google.com/docs/java.html#java_sdk_library_import Bug: 172797653 Change-Id: I0349e405fd290d427fa0b38a109b4212aace50c6
189 lines
5.9 KiB
Go
189 lines
5.9 KiB
Go
// Copyright 2019 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 bpdoc
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestExcludeByTag(t *testing.T) {
|
|
r := NewReader(pkgFiles)
|
|
ps, err := r.PropertyStruct(pkgPath, "tagTestProps", reflect.ValueOf(tagTestProps{}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ps.ExcludeByTag("tag1", "a")
|
|
|
|
expected := []string{"c", "d", "g"}
|
|
actual := actualProperties(t, ps.Properties)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("unexpected ExcludeByTag result, expected: %q, actual: %q", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestIncludeByTag(t *testing.T) {
|
|
r := NewReader(pkgFiles)
|
|
ps, err := r.PropertyStruct(pkgPath, "tagTestProps", reflect.ValueOf(tagTestProps{A: "B"}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ps.IncludeByTag("tag1", "c")
|
|
|
|
expected := []string{"b", "c", "d", "f", "g"}
|
|
actual := actualProperties(t, ps.Properties)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("unexpected IncludeByTag result, expected: %q, actual: %q", expected, actual)
|
|
}
|
|
}
|
|
|
|
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()
|
|
|
|
actual := []string{}
|
|
for _, p := range props {
|
|
actual = append(actual, p.Name)
|
|
actual = append(actual, actualProperties(t, p.Properties)...)
|
|
}
|
|
return actual
|
|
}
|