Skip to content

Commit

Permalink
Webhooko API Coverage Tool: Coverage Calculation (knative#426)
Browse files Browse the repository at this point in the history
This changeset adds to support to perform coverage calculation based on
the TypeCoverage type. This change also includes support to provide
repos an ability to ignore certain fields based on their needs.
  • Loading branch information
dushyanthsc authored and knative-prow-robot committed Jan 30, 2019
1 parent 2754247 commit 1a10543
Show file tree
Hide file tree
Showing 18 changed files with 343 additions and 38 deletions.
25 changes: 20 additions & 5 deletions tools/webhook-apicoverage/coveragecalculator/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
# Coverage Calculator

`coveragecalculator` package contains types and helper methods pertaining to
coverage calculation. Package includes type [TypeCoverage](coveragedata.go)
to represent coverage data for a particular API object type. This is the
wire contract between the webhook server running inside the K8 cluster and any
client using the API-Coverage tool. All API calls into the webhook-server would
return response containing this object to represent coverage data.
coverage calculation.

[TypeCoverage](coveragedata.go) is a type to represent coverage data for a
particular API object type. This is the wire contract between the webhook
server running inside the K8s cluster and any client using the API-Coverage
tool. All API calls into the webhook-server would return response containing
this object to represent coverage data.

[IgnoredFields](ignorefields.go) type provides ability for individual repos to
specify fields that they would like the API Coverage tool to ignore for
coverage calculation. Individual repos are expected to provide a .yaml
file providing fields that they would like to ignore and use helper method
`ReadFromFile(filePath)` to read and intialize this type. `FieldIgnored()` can
then be called by providing `packageName`, `typeName` and `FieldName` to check
if the field needs to be ignored.

[CalculateCoverage](calculator.go) method provides a capability to calculate
coverage values. This method takes an array of [TypeCoverage](coveragedata.go)
and iterates over them to aggreage coverage values. The aggregate result is
encapsulated inside [CoverageValues](calculator.go) and returned.
42 changes: 42 additions & 0 deletions tools/webhook-apicoverage/coveragecalculator/calculator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Copyright 2019 The Knative Authors
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 coveragecalculator

// CoverageValues encapsulates all the coverage related values.
type CoverageValues struct {
TotalFields int
CoveredFields int
IgnoredFields int
}

// CalculateTypeCoverage calculates aggregate coverage values based on provided []TypeCoverage
func CalculateTypeCoverage(typeCoverage []TypeCoverage) *CoverageValues {
cv := CoverageValues{}
for _, coverage := range typeCoverage {
for _, field := range coverage.Fields {
if field.Ignored {
cv.IgnoredFields++
} else {
cv.TotalFields++
if field.Coverage {
cv.CoveredFields++
}
}
}
}
return &cv
}
17 changes: 17 additions & 0 deletions tools/webhook-apicoverage/coveragecalculator/coveragedata.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
/*
Copyright 2019 The Knative Authors
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 coveragecalculator

// FieldCoverage represents coverage data for a field.
type FieldCoverage struct {
Field string `json:"Field"`
Values []string `json:"Values"`
Coverage bool `json:"Covered"`
Ignored bool `json:"Ignored"`
}

// Merge operation merges the field coverage data when multiple nodes represent the same type. (e.g. ConnectedNodes traversal)
Expand Down
80 changes: 80 additions & 0 deletions tools/webhook-apicoverage/coveragecalculator/ignorefields.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
Copyright 2019 The Knative Authors
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 coveragecalculator

import (
"fmt"
"io/ioutil"
"strings"

"gopkg.in/yaml.v2"
)

// IgnoredFields encapsulates fields to be ignored in a package for API coverage calculation.
type IgnoredFields struct {
ignoredFields map[string]map[string]bool
}


// This type is used for deserialization from the input .yaml file
type inputIgnoredFields struct {
Package string `yaml:"package"`
Type string `yaml:"type"`
Fields []string `yaml:"fields"`
}

// ReadFromFile is a utility method that can be used by repos to read .yaml input file into
// IgnoredFields type.
func (ig *IgnoredFields) ReadFromFile(filePath string) error {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return fmt.Errorf("Error reading file: %s Error : %v", filePath, err)
}

var inputEntries []inputIgnoredFields
err = yaml.Unmarshal(data, &inputEntries)
if err != nil {
return fmt.Errorf("Error unmarshalling ignoredfields input yaml file: %s Content: %s Error: %v", filePath, string(data), err)
}

ig.ignoredFields = map[string]map[string]bool {}

for _, entry := range inputEntries {
if _, ok := ig.ignoredFields[entry.Package]; !ok {
ig.ignoredFields[entry.Package] = map[string]bool{}
}

for _, field := range entry.Fields {
ig.ignoredFields[entry.Package][entry.Type + "." + field] = true
}
}
return nil
}

// FieldIgnored method given a package, type and field returns true if the field is marked ignored.
func (ig *IgnoredFields) FieldIgnored(packageName string, typeName string, fieldName string) bool {
if ig.ignoredFields != nil {
for key, value := range ig.ignoredFields {
if strings.HasSuffix(packageName, key) {
if _, ok := value[typeName + "." + fieldName]; ok {
return true
}
}
}
}
return false
}
7 changes: 4 additions & 3 deletions tools/webhook-apicoverage/resourcetree/arraykindnode.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2018 The Knative Authors
Copyright 2019 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -58,9 +58,10 @@ func (a *ArrayKindNode) updateCoverage(v reflect.Value) {
}
}

func (a *ArrayKindNode) buildCoverageData(typeCoverage []coveragecalculator.TypeCoverage, nodeRules NodeRules, fieldRules FieldRules) {
func (a *ArrayKindNode) buildCoverageData(typeCoverage *[]coveragecalculator.TypeCoverage, nodeRules NodeRules,
fieldRules FieldRules, ignoredFields coveragecalculator.IgnoredFields) {
if a.arrKind == reflect.Struct {
a.Children[a.Field + arrayNodeNameSuffix].buildCoverageData(typeCoverage, nodeRules, fieldRules)
a.Children[a.Field + arrayNodeNameSuffix].buildCoverageData(typeCoverage, nodeRules, fieldRules, ignoredFields)
}
}

Expand Down
5 changes: 3 additions & 2 deletions tools/webhook-apicoverage/resourcetree/basictypekindnode.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2018 The Knative Authors
Copyright 2019 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -59,7 +59,8 @@ func (b *BasicTypeKindNode) updateCoverage(v reflect.Value) {
}

// no-op as the coverage is calculated as field coverage in parent node.
func (b *BasicTypeKindNode) buildCoverageData(typeCoverage []coveragecalculator.TypeCoverage, nodeRules NodeRules, fieldRules FieldRules) {}
func (b *BasicTypeKindNode) buildCoverageData(typeCoverage *[]coveragecalculator.TypeCoverage, nodeRules NodeRules,
fieldRules FieldRules, ignoredFields coveragecalculator.IgnoredFields) {}

func (b *BasicTypeKindNode) string(v reflect.Value) string {
switch v.Kind() {
Expand Down
5 changes: 3 additions & 2 deletions tools/webhook-apicoverage/resourcetree/node.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2018 The Knative Authors
Copyright 2019 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -30,7 +30,8 @@ type NodeInterface interface {
initialize(field string, parent NodeInterface, t reflect.Type, rt *ResourceTree)
buildChildNodes(t reflect.Type)
updateCoverage(v reflect.Value)
buildCoverageData(typeCoverage []coveragecalculator.TypeCoverage, nodeRules NodeRules, fieldRules FieldRules)
buildCoverageData(typeCoverage *[]coveragecalculator.TypeCoverage, nodeRules NodeRules,
fieldRules FieldRules, ignoredFields coveragecalculator.IgnoredFields)
getValues() ([]string)
}

Expand Down
5 changes: 3 additions & 2 deletions tools/webhook-apicoverage/resourcetree/otherkindnode.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2018 The Knative Authors
Copyright 2019 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -46,7 +46,8 @@ func (o *OtherKindNode) updateCoverage(v reflect.Value) {
}

// no-op as the coverage is calculated as field coverage in parent node.
func (o * OtherKindNode) buildCoverageData(typeCoverage []coveragecalculator.TypeCoverage, nodeRules NodeRules, fieldRules FieldRules) {}
func (o * OtherKindNode) buildCoverageData(typeCoverage *[]coveragecalculator.TypeCoverage, nodeRules NodeRules,
fieldRules FieldRules, ignoredFields coveragecalculator.IgnoredFields) {}

func (o *OtherKindNode) getValues() ([]string) {
return nil
Expand Down
7 changes: 4 additions & 3 deletions tools/webhook-apicoverage/resourcetree/ptrkindnode.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2018 The Knative Authors
Copyright 2019 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -56,9 +56,10 @@ func (p *PtrKindNode) updateCoverage(v reflect.Value) {
}
}

func (p *PtrKindNode) buildCoverageData(typeCoverage []coveragecalculator.TypeCoverage, nodeRules NodeRules, fieldRules FieldRules) {
func (p *PtrKindNode) buildCoverageData(typeCoverage *[]coveragecalculator.TypeCoverage, nodeRules NodeRules,
fieldRules FieldRules, ignoredFields coveragecalculator.IgnoredFields) {
if p.objKind == reflect.Struct {
p.Children[p.Field + ptrNodeNameSuffix].buildCoverageData(typeCoverage, nodeRules, fieldRules)
p.Children[p.Field + ptrNodeNameSuffix].buildCoverageData(typeCoverage, nodeRules, fieldRules, ignoredFields)
}
}

Expand Down
16 changes: 10 additions & 6 deletions tools/webhook-apicoverage/resourcetree/resourceforest.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2018 The Knative Authors
Copyright 2019 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -33,10 +33,12 @@ type ResourceForest struct {

// getConnectedNodeCoverage calculates the outlined coverage for a Type using ConnectedNodes linkedlist. We traverse through each element in the linkedlist and merge
// coverage data into a single coveragecalculator.TypeCoverage object.
func (r *ResourceForest) getConnectedNodeCoverage(fieldType reflect.Type, fieldRules FieldRules) (coveragecalculator.TypeCoverage){
coverage := coveragecalculator.TypeCoverage{
func (r *ResourceForest) getConnectedNodeCoverage(fieldType reflect.Type, fieldRules FieldRules, ignoredFields coveragecalculator.IgnoredFields) (coveragecalculator.TypeCoverage) {
packageName := strings.Replace(fieldType.PkgPath(), "/", ".", -1)
coverage := coveragecalculator.TypeCoverage {
Type: fieldType.Name(),
Package: strings.Replace(fieldType.PkgPath(), "/", ".", -1),
Package: packageName,
Fields : make(map[string]*coveragecalculator.FieldCoverage),
}

if value, ok := r.ConnectedNodes[fieldType.PkgPath() + "." + fieldType.Name()]; ok {
Expand All @@ -45,14 +47,16 @@ func (r *ResourceForest) getConnectedNodeCoverage(fieldType reflect.Type, fieldR
for field, v := range node.GetData().Children {
if fieldRules.Apply(field) {
if _, ok := coverage.Fields[field]; !ok {
coverage.Fields[field] = new(coveragecalculator.FieldCoverage)
coverage.Fields[field] = &coveragecalculator.FieldCoverage {
Field: field,
Ignored: ignoredFields.FieldIgnored(packageName, fieldType.Name(), field),
}
}
// merge values across the list.
coverage.Fields[field].Merge(v.GetData().Covered, v.getValues())
}
}
}
}

return coverage
}
7 changes: 4 additions & 3 deletions tools/webhook-apicoverage/resourcetree/resourcetree.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2018 The Knative Authors
Copyright 2019 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -72,8 +72,9 @@ func (r *ResourceTree) UpdateCoverage(v reflect.Value) {
}

// BuildCoverageData calculates the coverage information for a resource tree by applying provided Node and Field rules.
func (r *ResourceTree) BuildCoverageData(nodeRules NodeRules, fieldRules FieldRules) ([]coveragecalculator.TypeCoverage) {
func (r *ResourceTree) BuildCoverageData(nodeRules NodeRules, fieldRules FieldRules,
ignoredFields coveragecalculator.IgnoredFields) ([]coveragecalculator.TypeCoverage) {
typeCoverage := []coveragecalculator.TypeCoverage{}
r.Root.buildCoverageData(typeCoverage, nodeRules, fieldRules)
r.Root.buildCoverageData(&typeCoverage, nodeRules, fieldRules, ignoredFields)
return typeCoverage
}
24 changes: 20 additions & 4 deletions tools/webhook-apicoverage/resourcetree/rule.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
/*
Copyright 2019 The Knative Authors
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 resourcetree

// rule.go contains different rules that can be defined to control resource tree traversal.

// NodeRules encapsulates all the node level rules defined by a repo.
type NodeRules struct {
rules []func(nodeInterface NodeInterface) bool
Rules []func(nodeInterface NodeInterface) bool
}

// Apply runs all the rules defined by a repo against a node.
func (n *NodeRules) Apply(node NodeInterface) bool {
for _, rule := range n.rules {
for _, rule := range n.Rules {
if !rule(node) {
return false
}
Expand All @@ -19,12 +35,12 @@ func (n *NodeRules) Apply(node NodeInterface) bool {

// FieldRules encapsulates all the field level rules defined by a repo.
type FieldRules struct {
rules []func(fieldName string) bool
Rules []func(fieldName string) bool
}

// Apply runs all the rules defined by a repo against a field.
func (f *FieldRules) Apply(fieldName string) bool {
for _, rule := range f.rules {
for _, rule := range f.Rules {
if !rule(fieldName) {
return false
}
Expand Down
Loading

0 comments on commit 1a10543

Please sign in to comment.