examples_test: Validate all parts in a multi-doc YAML.

This commit is contained in:
Anthony Yeh 2016-11-22 13:17:03 -08:00 committed by Devin Donnelly
parent 8b3f6b5fd6
commit b1fc42ecc8
2 changed files with 136 additions and 110 deletions

View File

@ -14,8 +14,8 @@ spec:
selector: selector:
app: nginx app: nginx
--- ---
apiVersion: apps/v1alpha1 apiVersion: apps/v1beta1
kind: PetSet kind: StatefulSet
metadata: metadata:
name: web name: web
spec: spec:

View File

@ -17,7 +17,10 @@ limitations under the License.
package examples_test package examples_test
import ( import (
"bufio"
"bytes"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -153,7 +156,7 @@ func validateObject(obj runtime.Object) (errors field.ErrorList) {
// Walks inDir for any json/yaml files. Converts yaml to json, and calls fn for // Walks inDir for any json/yaml files. Converts yaml to json, and calls fn for
// each file found with the contents in data. // each file found with the contents in data.
func walkConfigFiles(inDir string, fn func(name, path string, data []byte)) error { func walkConfigFiles(inDir string, fn func(name, path string, data [][]byte)) error {
return filepath.Walk(inDir, func(path string, info os.FileInfo, err error) error { return filepath.Walk(inDir, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
@ -172,137 +175,151 @@ func walkConfigFiles(inDir string, fn func(name, path string, data []byte)) erro
} }
name := strings.TrimSuffix(file, ext) name := strings.TrimSuffix(file, ext)
var docs [][]byte
if ext == ".yaml" { if ext == ".yaml" {
out, err := yaml.ToJSON(data) // YAML can contain multiple documents.
if err != nil { splitter := yaml.NewYAMLReader(bufio.NewReader(bytes.NewBuffer(data)))
return fmt.Errorf("%s: %v", path, err) for {
doc, err := splitter.Read()
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("%s: %v", path, err)
}
out, err := yaml.ToJSON(doc)
if err != nil {
return fmt.Errorf("%s: %v", path, err)
}
docs = append(docs, out)
} }
data = out } else {
docs = append(docs, data)
} }
fn(name, path, data) fn(name, path, docs)
} }
return nil return nil
}) })
} }
func TestExampleObjectSchemas(t *testing.T) { func TestExampleObjectSchemas(t *testing.T) {
cases := map[string]map[string]runtime.Object{ cases := map[string]map[string][]runtime.Object{
"../docs/user-guide/walkthrough": { "../docs/user-guide/walkthrough": {
"deployment": &extensions.Deployment{}, "deployment": {&extensions.Deployment{}},
"deployment-update": &extensions.Deployment{}, "deployment-update": {&extensions.Deployment{}},
"pod-nginx": &api.Pod{}, "pod-nginx": {&api.Pod{}},
"pod-nginx-with-label": &api.Pod{}, "pod-nginx-with-label": {&api.Pod{}},
"pod-redis": &api.Pod{}, "pod-redis": {&api.Pod{}},
"pod-with-http-healthcheck": &api.Pod{}, "pod-with-http-healthcheck": {&api.Pod{}},
"podtemplate": &api.PodTemplate{}, "podtemplate": {&api.PodTemplate{}},
"service": &api.Service{}, "service": {&api.Service{}},
}, },
"../docs/user-guide/update-demo": { "../docs/user-guide/update-demo": {
"kitten-rc": &api.ReplicationController{}, "kitten-rc": {&api.ReplicationController{}},
"nautilus-rc": &api.ReplicationController{}, "nautilus-rc": {&api.ReplicationController{}},
}, },
"../docs/user-guide/persistent-volumes/volumes": { "../docs/user-guide/persistent-volumes/volumes": {
"local-01": &api.PersistentVolume{}, "local-01": {&api.PersistentVolume{}},
"local-02": &api.PersistentVolume{}, "local-02": {&api.PersistentVolume{}},
"gce": &api.PersistentVolume{}, "gce": {&api.PersistentVolume{}},
"nfs": &api.PersistentVolume{}, "nfs": {&api.PersistentVolume{}},
}, },
"../docs/user-guide/persistent-volumes/claims": { "../docs/user-guide/persistent-volumes/claims": {
"claim-01": &api.PersistentVolumeClaim{}, "claim-01": {&api.PersistentVolumeClaim{}},
"claim-02": &api.PersistentVolumeClaim{}, "claim-02": {&api.PersistentVolumeClaim{}},
"claim-03": &api.PersistentVolumeClaim{}, "claim-03": {&api.PersistentVolumeClaim{}},
}, },
"../docs/user-guide/persistent-volumes/simpletest": { "../docs/user-guide/persistent-volumes/simpletest": {
"namespace": &api.Namespace{}, "namespace": {&api.Namespace{}},
"pod": &api.Pod{}, "pod": {&api.Pod{}},
"service": &api.Service{}, "service": {&api.Service{}},
}, },
"../docs/user-guide/liveness": { "../docs/user-guide/liveness": {
"exec-liveness": &api.Pod{}, "exec-liveness": {&api.Pod{}},
"http-liveness": &api.Pod{}, "http-liveness": {&api.Pod{}},
"http-liveness-named-port": &api.Pod{}, "http-liveness-named-port": {&api.Pod{}},
}, },
"../docs/user-guide/jobs/work-queue-1": { "../docs/user-guide/jobs/work-queue-1": {
"job": &batch.Job{}, "job": {&batch.Job{}},
}, },
"../docs/user-guide/jobs/work-queue-2": { "../docs/user-guide/jobs/work-queue-2": {
"job": &batch.Job{}, "job": {&batch.Job{}},
"redis-pod": &api.Pod{}, "redis-pod": {&api.Pod{}},
"redis-service": &api.Service{}, "redis-service": {&api.Service{}},
}, },
"../docs/user-guide": { "../docs/user-guide": {
"bad-nginx-deployment": &extensions.Deployment{}, "bad-nginx-deployment": {&extensions.Deployment{}},
"counter-pod": &api.Pod{}, "counter-pod": {&api.Pod{}},
"curlpod": &extensions.Deployment{}, "curlpod": {&extensions.Deployment{}},
"deployment": &extensions.Deployment{}, "deployment": {&extensions.Deployment{}},
"ingress": &extensions.Ingress{}, "ingress": {&extensions.Ingress{}},
"job": &batch.Job{}, "job": {&batch.Job{}},
"multi-pod": &api.Pod{}, "multi-pod": {&api.Pod{}, &api.Pod{}},
"new-nginx-deployment": &extensions.Deployment{}, "new-nginx-deployment": {&extensions.Deployment{}},
"nginx-app": &api.Service{}, "nginx-app": {&api.Service{}, &extensions.Deployment{}},
"nginx-deployment": &extensions.Deployment{}, "nginx-deployment": {&extensions.Deployment{}},
"nginx-init-containers": &api.Pod{}, "nginx-init-containers": {&api.Pod{}},
"nginx-lifecycle-deployment": &extensions.Deployment{}, "nginx-lifecycle-deployment": {&extensions.Deployment{}},
"nginx-probe-deployment": &extensions.Deployment{}, "nginx-probe-deployment": {&extensions.Deployment{}},
"nginx-secure-app": &api.Service{}, "nginx-secure-app": {&api.Service{}, &extensions.Deployment{}},
"nginx-svc": &api.Service{}, "nginx-svc": {&api.Service{}},
"petset": &api.Service{}, "petset": {&api.Service{}, &apps.StatefulSet{}},
"pod": &api.Pod{}, "pod": {&api.Pod{}},
"pod-w-message": &api.Pod{}, "pod-w-message": {&api.Pod{}},
"redis-deployment": &extensions.Deployment{}, "redis-deployment": {&extensions.Deployment{}},
"redis-resource-deployment": &extensions.Deployment{}, "redis-resource-deployment": {&extensions.Deployment{}},
"redis-secret-deployment": &extensions.Deployment{}, "redis-secret-deployment": {&extensions.Deployment{}},
"run-my-nginx": &extensions.Deployment{}, "run-my-nginx": {&extensions.Deployment{}},
"cronjob": &batch.CronJob{}, "cronjob": {&batch.CronJob{}},
}, },
"../docs/admin": { "../docs/admin": {
"daemon": &extensions.DaemonSet{}, "daemon": {&extensions.DaemonSet{}},
}, },
"../docs/user-guide/downward-api": { "../docs/user-guide/downward-api": {
"dapi-pod": &api.Pod{}, "dapi-pod": {&api.Pod{}},
"dapi-container-resources": &api.Pod{}, "dapi-container-resources": {&api.Pod{}},
}, },
"../docs/user-guide/downward-api/volume/": { "../docs/user-guide/downward-api/volume/": {
"dapi-volume": &api.Pod{}, "dapi-volume": {&api.Pod{}},
"dapi-volume-resources": &api.Pod{}, "dapi-volume-resources": {&api.Pod{}},
}, },
"../docs/admin/namespaces": { "../docs/admin/namespaces": {
"namespace-dev": &api.Namespace{}, "namespace-dev": {&api.Namespace{}},
"namespace-prod": &api.Namespace{}, "namespace-prod": {&api.Namespace{}},
}, },
"../docs/admin/limitrange": { "../docs/admin/limitrange": {
"invalid-pod": &api.Pod{}, "invalid-pod": {&api.Pod{}},
"limits": &api.LimitRange{}, "limits": {&api.LimitRange{}},
"namespace": &api.Namespace{}, "namespace": {&api.Namespace{}},
"valid-pod": &api.Pod{}, "valid-pod": {&api.Pod{}},
}, },
"../docs/user-guide/logging-demo": { "../docs/user-guide/logging-demo": {
"synthetic_0_25lps": &api.Pod{}, "synthetic_0_25lps": {&api.Pod{}},
"synthetic_10lps": &api.Pod{}, "synthetic_10lps": {&api.Pod{}},
}, },
"../docs/user-guide/node-selection": { "../docs/user-guide/node-selection": {
"pod": &api.Pod{}, "pod": {&api.Pod{}},
"pod-with-node-affinity": &api.Pod{}, "pod-with-node-affinity": {&api.Pod{}},
"pod-with-pod-affinity": &api.Pod{}, "pod-with-pod-affinity": {&api.Pod{}},
}, },
"../docs/admin/resourcequota": { "../docs/admin/resourcequota": {
"best-effort": &api.ResourceQuota{}, "best-effort": {&api.ResourceQuota{}},
"compute-resources": &api.ResourceQuota{}, "compute-resources": {&api.ResourceQuota{}},
"limits": &api.LimitRange{}, "limits": {&api.LimitRange{}},
"namespace": &api.Namespace{}, "namespace": {&api.Namespace{}},
"not-best-effort": &api.ResourceQuota{}, "not-best-effort": {&api.ResourceQuota{}},
"object-counts": &api.ResourceQuota{}, "object-counts": {&api.ResourceQuota{}},
}, },
"../docs/user-guide/secrets": { "../docs/user-guide/secrets": {
"secret-pod": &api.Pod{}, "secret-pod": {&api.Pod{}},
"secret": &api.Secret{}, "secret": {&api.Secret{}},
"secret-env-pod": &api.Pod{}, "secret-env-pod": {&api.Pod{}},
}, },
"../docs/tutorials/replicated-stateful-application": { "../docs/tutorials/replicated-stateful-application": {
"mysql-services": &api.Service{}, "mysql-services": {&api.Service{}, &api.Service{}},
"mysql-configmap": &api.ConfigMap{}, "mysql-configmap": {&api.ConfigMap{}},
"mysql-statefulset": &apps.StatefulSet{}, "mysql-statefulset": {&apps.StatefulSet{}},
}, },
} }
@ -312,43 +329,52 @@ func TestExampleObjectSchemas(t *testing.T) {
for path, expected := range cases { for path, expected := range cases {
tested := 0 tested := 0
err := walkConfigFiles(path, func(name, path string, data []byte) { numExpected := 0
expectedType, found := expected[name] err := walkConfigFiles(path, func(name, path string, docs [][]byte) {
expectedTypes, found := expected[name]
if !found { if !found {
t.Errorf("%s: %s does not have a test case defined", path, name) t.Errorf("%s: %s does not have a test case defined", path, name)
return return
} }
tested++ numExpected += len(expectedTypes)
if expectedType == nil { if len(expectedTypes) != len(docs) {
t.Logf("skipping : %s/%s\n", path, name) t.Errorf("%s: number of expected types (%v) doesn't match number of docs in YAML (%v)", path, len(expectedTypes), len(docs))
return return
} }
if strings.Contains(name, "scheduler-policy-config") { for i, data := range docs {
if err := runtime.DecodeInto(schedulerapilatest.Codec, data, expectedType); err != nil { expectedType := expectedTypes[i]
t.Errorf("%s did not decode correctly: %v\n%s", path, err, string(data)) tested++
if expectedType == nil {
t.Logf("skipping : %s/%s\n", path, name)
return return
} }
// TODO: Add validate method for if strings.Contains(name, "scheduler-policy-config") {
// &schedulerapi.Policy, and remove this if err := runtime.DecodeInto(schedulerapilatest.Codec, data, expectedType); err != nil {
// special case t.Errorf("%s did not decode correctly: %v\n%s", path, err, string(data))
} else { return
codec, err := testapi.GetCodecForObject(expectedType) }
if err != nil { // TODO: Add validate method for
t.Errorf("Could not get codec for %s: %s", expectedType, err) // &schedulerapi.Policy, and remove this
} // special case
if err := runtime.DecodeInto(codec, data, expectedType); err != nil { } else {
t.Errorf("%s did not decode correctly: %v\n%s", path, err, string(data)) codec, err := testapi.GetCodecForObject(expectedType)
return if err != nil {
} t.Errorf("Could not get codec for %s: %s", expectedType, err)
if errors := validateObject(expectedType); len(errors) > 0 { }
t.Errorf("%s did not validate correctly: %v", path, errors) if err := runtime.DecodeInto(codec, data, expectedType); err != nil {
t.Errorf("%s did not decode correctly: %v\n%s", path, err, string(data))
return
}
if errors := validateObject(expectedType); len(errors) > 0 {
t.Errorf("%s did not validate correctly: %v", path, errors)
}
} }
} }
}) })
if err != nil { if err != nil {
t.Errorf("Expected no error, Got %v on Path %v", err, path) t.Errorf("Expected no error, Got %v on Path %v", err, path)
} }
if tested != len(expected) { if tested != numExpected {
t.Errorf("Directory %v: Expected %d examples, Got %d", path, len(expected), tested) t.Errorf("Directory %v: Expected %d examples, Got %d", path, len(expected), tested)
} }
} }