Merge pull request #87578 from Jefftree/bdd-tooling

Add tooling around validation for Behavior Driven Conformance
This commit is contained in:
Kubernetes Prow Robot 2020-02-18 14:04:25 -08:00 committed by GitHub
commit 1b2a81e6ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 134 additions and 27 deletions

View File

@ -24,6 +24,7 @@ filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//test/conformance/behaviors:all-srcs",
"//test/conformance/kubetestgen:all-srcs",
],
tags = ["automanaged"],

View File

@ -0,0 +1,32 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["types.go"],
importpath = "k8s.io/kubernetes/test/conformance/behaviors",
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["behaviors_test.go"],
data = glob(["*/*.yaml"]),
embed = [":go_default_library"],
deps = [
"//vendor/gopkg.in/yaml.v2:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,75 @@
/*
Copyright 2020 The Kubernetes 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 behaviors
import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"testing"
"gopkg.in/yaml.v2"
)
func TestValidate(t *testing.T) {
var behaviorFiles []string
err := filepath.Walk(".",
func(path string, info os.FileInfo, err error) error {
if err != nil {
t.Errorf("%q", err.Error())
}
r, _ := regexp.Compile(".+.yaml$")
if r.MatchString(path) {
behaviorFiles = append(behaviorFiles, path)
}
return nil
})
if err != nil {
t.Errorf("%q", err.Error())
}
for _, file := range behaviorFiles {
validateSuite(file, t)
}
}
func validateSuite(path string, t *testing.T) {
var suite Suite
yamlFile, err := ioutil.ReadFile(path)
if err != nil {
t.Errorf("%q", err.Error())
}
err = yaml.UnmarshalStrict(yamlFile, &suite)
if err != nil {
t.Errorf("%q", err.Error())
}
behaviorIDList := make(map[string]bool)
for _, behavior := range suite.Behaviors {
// Ensure no behavior IDs are duplicated
if _, ok := behaviorIDList[behavior.ID]; ok {
t.Errorf("Duplicate behavior ID: %s", behavior.ID)
}
behaviorIDList[behavior.ID] = true
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2019 The Kubernetes Authors.
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package behaviors
// Area is a conformance area composed of a list of test suites
type Area struct {

View File

@ -2,13 +2,11 @@ load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
go_library(
name = "go_default_library",
srcs = [
"kubetestgen.go",
"types.go",
],
srcs = ["kubetestgen.go"],
importpath = "k8s.io/kubernetes/test/conformance/kubetestgen",
visibility = ["//visibility:private"],
deps = [
"//test/conformance/behaviors:go_default_library",
"//vendor/github.com/go-openapi/analysis:go_default_library",
"//vendor/github.com/go-openapi/loads:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library",

View File

@ -27,6 +27,7 @@ import (
"github.com/go-openapi/loads"
"github.com/go-openapi/spec"
"gopkg.in/yaml.v2"
"k8s.io/kubernetes/test/conformance/behaviors"
)
type options struct {
@ -67,8 +68,8 @@ func main() {
defMap[d.Ref.String()] = d
}
var suites []Suite
var suiteMapping = make(map[string]*Suite)
var suites []behaviors.Suite
var suiteMapping = make(map[string]*behaviors.Suite)
for _, v := range defs {
if !v.TopLevel || o.resource != v.Name {
@ -76,10 +77,10 @@ func main() {
}
name := trimObjectName(v.Name)
defaultsuite := Suite{
defaultsuite := behaviors.Suite{
Suite: o.area + "/spec",
Description: "Base suite for " + o.area,
Behaviors: []Behavior{},
Behaviors: []behaviors.Behavior{},
}
_ = defaultsuite
@ -89,10 +90,10 @@ func main() {
if propSchema.Ref.String() != "" || propSchema.Type[0] == "array" {
if _, ok := suiteMapping[id]; !ok {
newsuite := Suite{
newsuite := behaviors.Suite{
Suite: o.area + "/" + p,
Description: "Suite for " + o.area + "/" + p,
Behaviors: []Behavior{},
Behaviors: []behaviors.Behavior{},
}
suiteMapping[id] = &newsuite
}
@ -101,10 +102,10 @@ func main() {
suiteMapping[id].Behaviors = behaviors
} else {
if _, ok := suiteMapping["default"]; !ok {
newsuite := Suite{
newsuite := behaviors.Suite{
Suite: o.area + "/spec",
Description: "Base suite for " + o.area,
Behaviors: []Behavior{},
Behaviors: []behaviors.Behavior{},
}
suiteMapping["default"] = &newsuite
}
@ -122,12 +123,12 @@ func main() {
break
}
var area Area = Area{o.area, suites}
var area behaviors.Area = behaviors.Area{Area: o.area, Suites: suites}
countFields(suites)
printYAML(o.behaviorsDir+o.area, area)
}
func printYAML(fileName string, areaO Area) {
func printYAML(fileName string, areaO behaviors.Area) {
f, err := os.Create(fileName + ".yaml")
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
@ -147,7 +148,7 @@ func printYAML(fileName string, areaO Area) {
}
}
func countFields(suites []Suite) {
func countFields(suites []behaviors.Suite) {
var fieldsMapping map[string]int
fieldsMapping = make(map[string]int)
for _, suite := range suites {
@ -174,21 +175,21 @@ func trimObjectName(name string) string {
return name
}
func objectBehaviors(id string, s *spec.Schema) []Behavior {
func objectBehaviors(id string, s *spec.Schema) []behaviors.Behavior {
if strings.Contains(id, "openAPIV3Schema") || strings.Contains(id, "JSONSchema") || strings.Contains(s.Ref.String(), "JSONSchema") {
return []Behavior{}
return []behaviors.Behavior{}
}
ref, ok := defMap[s.Ref.String()]
if !ok {
return []Behavior{}
return []behaviors.Behavior{}
}
return schemaBehaviors(id, trimObjectName(ref.Name), ref.Schema)
}
func schemaBehaviors(base, apiObject string, s *spec.Schema) []Behavior {
var behaviors []Behavior
func schemaBehaviors(base, apiObject string, s *spec.Schema) []behaviors.Behavior {
var behaviors []behaviors.Behavior
for p, propSchema := range s.Properties {
b := schemaBehavior(base, apiObject, p, propSchema)
behaviors = append(behaviors, b...)
@ -196,21 +197,21 @@ func schemaBehaviors(base, apiObject string, s *spec.Schema) []Behavior {
return behaviors
}
func schemaBehavior(base, apiObject, p string, propSchema spec.Schema) []Behavior {
func schemaBehavior(base, apiObject, p string, propSchema spec.Schema) []behaviors.Behavior {
id := strings.Join([]string{base, p}, "/")
if propSchema.Ref.String() != "" {
if apiObject == trimObjectName(propSchema.Ref.String()) {
return []Behavior{}
return []behaviors.Behavior{}
}
return objectBehaviors(id, &propSchema)
}
var b []Behavior
var b []behaviors.Behavior
switch propSchema.Type[0] {
case "array":
b = objectBehaviors(id, propSchema.Items.Schema)
case "boolean":
b = []Behavior{
b = []behaviors.Behavior{
{
ID: id,
APIObject: apiObject,
@ -227,7 +228,7 @@ func schemaBehavior(base, apiObject, p string, propSchema spec.Schema) []Behavio
},
}
default:
b = []Behavior{{
b = []behaviors.Behavior{{
ID: id,
APIObject: apiObject,
APIField: p,