package api_test

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"math/rand"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/google/gofuzz"
	kapi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/meta"
	"k8s.io/kubernetes/pkg/api/testapi"
	apitesting "k8s.io/kubernetes/pkg/api/testing"
	"k8s.io/kubernetes/pkg/api/unversioned"
	"k8s.io/kubernetes/pkg/api/validation"
	"k8s.io/kubernetes/pkg/runtime"
	"k8s.io/kubernetes/pkg/runtime/serializer/protobuf"
	"k8s.io/kubernetes/pkg/types"
	"k8s.io/kubernetes/pkg/util/diff"
	"k8s.io/kubernetes/pkg/util/intstr"
	"k8s.io/kubernetes/pkg/util/sets"

	osapi "github.com/openshift/origin/pkg/api"
	_ "github.com/openshift/origin/pkg/api/latest"
	"github.com/openshift/origin/pkg/api/v1"
	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
	build "github.com/openshift/origin/pkg/build/api"
	deploy "github.com/openshift/origin/pkg/deploy/api"
	image "github.com/openshift/origin/pkg/image/api"
	"github.com/openshift/origin/pkg/image/api/docker10"
	"github.com/openshift/origin/pkg/image/api/dockerpre012"
	oauthapi "github.com/openshift/origin/pkg/oauth/api"
	quotaapi "github.com/openshift/origin/pkg/quota/api"
	quotaapiv1 "github.com/openshift/origin/pkg/quota/api/v1"
	route "github.com/openshift/origin/pkg/route/api"
	securityapi "github.com/openshift/origin/pkg/security/api"
	template "github.com/openshift/origin/pkg/template/api"
	uservalidation "github.com/openshift/origin/pkg/user/api/validation"

	// install all APIs
	_ "github.com/openshift/origin/pkg/api/install"
	_ "github.com/openshift/origin/pkg/quota/api/install"
	_ "k8s.io/kubernetes/pkg/api/install"
)

var codecsToTest = []func(version unversioned.GroupVersion, item runtime.Object) (runtime.Codec, error){
	func(version unversioned.GroupVersion, item runtime.Object) (runtime.Codec, error) {
		return kapi.Codecs.LegacyCodec(version), nil
	},
	func(version unversioned.GroupVersion, item runtime.Object) (runtime.Codec, error) {
		s := protobuf.NewSerializer(kapi.Scheme, kapi.Scheme, "application/arbitrary.content.type")
		return kapi.Codecs.CodecForVersions(s, s, testapi.ExternalGroupVersions(), nil), nil
	},
}

func fuzzInternalObject(t *testing.T, forVersion unversioned.GroupVersion, item runtime.Object, seed int64) runtime.Object {
	f := apitesting.FuzzerFor(t, forVersion, rand.NewSource(seed))
	f.Funcs(
		// Roles and RoleBindings maps are never nil
		func(j *authorizationapi.Policy, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			if j.Roles != nil {
				j.Roles = make(map[string]*authorizationapi.Role)
			}
			for k, v := range j.Roles {
				if v == nil {
					delete(j.Roles, k)
				}
			}
		},
		func(j *authorizationapi.PolicyBinding, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			if j.RoleBindings == nil {
				j.RoleBindings = make(map[string]*authorizationapi.RoleBinding)
			}
			for k, v := range j.RoleBindings {
				if v == nil {
					delete(j.RoleBindings, k)
				}
			}
		},
		func(j *authorizationapi.ClusterPolicy, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			if j.Roles == nil {
				j.Roles = make(map[string]*authorizationapi.ClusterRole)
			}
			for k, v := range j.Roles {
				if v == nil {
					delete(j.Roles, k)
				}
			}
		},
		func(j *authorizationapi.ClusterPolicyBinding, c fuzz.Continue) {
			j.RoleBindings = make(map[string]*authorizationapi.ClusterRoleBinding)
		},
		func(j *authorizationapi.RoleBinding, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			for i := range j.Subjects {
				kinds := []string{authorizationapi.UserKind, authorizationapi.SystemUserKind, authorizationapi.GroupKind, authorizationapi.SystemGroupKind, authorizationapi.ServiceAccountKind}
				j.Subjects[i].Kind = kinds[c.Intn(len(kinds))]
				switch j.Subjects[i].Kind {
				case authorizationapi.UserKind:
					j.Subjects[i].Namespace = ""
					if len(uservalidation.ValidateUserName(j.Subjects[i].Name, false)) != 0 {
						j.Subjects[i].Name = fmt.Sprintf("validusername%d", i)
					}

				case authorizationapi.GroupKind:
					j.Subjects[i].Namespace = ""
					if len(uservalidation.ValidateGroupName(j.Subjects[i].Name, false)) != 0 {
						j.Subjects[i].Name = fmt.Sprintf("validgroupname%d", i)
					}

				case authorizationapi.ServiceAccountKind:
					if len(validation.ValidateNamespaceName(j.Subjects[i].Namespace, false)) != 0 {
						j.Subjects[i].Namespace = fmt.Sprintf("sanamespacehere%d", i)
					}
					if len(validation.ValidateServiceAccountName(j.Subjects[i].Name, false)) != 0 {
						j.Subjects[i].Name = fmt.Sprintf("sanamehere%d", i)
					}

				case authorizationapi.SystemUserKind, authorizationapi.SystemGroupKind:
					j.Subjects[i].Namespace = ""
					j.Subjects[i].Name = ":" + j.Subjects[i].Name

				}

				j.Subjects[i].UID = types.UID("")
				j.Subjects[i].APIVersion = ""
				j.Subjects[i].ResourceVersion = ""
				j.Subjects[i].FieldPath = ""
			}
		},
		func(j *authorizationapi.PolicyRule, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			// if no groups are found, then we assume "".  This matches defaulting
			if len(j.APIGroups) == 0 {
				j.APIGroups = []string{""}
			}
			switch c.Intn(3) {
			case 0:
				j.AttributeRestrictions = &authorizationapi.IsPersonalSubjectAccessReview{}
			case 1:
				j.AttributeRestrictions = &runtime.Unknown{TypeMeta: runtime.TypeMeta{Kind: "Type", APIVersion: "other"}, ContentType: "application/json", Raw: []byte(`{"apiVersion":"other","kind":"Type"}`)}
			default:
				j.AttributeRestrictions = nil
			}
		},
		func(j *authorizationapi.ClusterRoleBinding, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			for i := range j.Subjects {
				kinds := []string{authorizationapi.UserKind, authorizationapi.SystemUserKind, authorizationapi.GroupKind, authorizationapi.SystemGroupKind, authorizationapi.ServiceAccountKind}
				j.Subjects[i].Kind = kinds[c.Intn(len(kinds))]
				switch j.Subjects[i].Kind {
				case authorizationapi.UserKind:
					j.Subjects[i].Namespace = ""
					if len(uservalidation.ValidateUserName(j.Subjects[i].Name, false)) != 0 {
						j.Subjects[i].Name = fmt.Sprintf("validusername%d", i)
					}

				case authorizationapi.GroupKind:
					j.Subjects[i].Namespace = ""
					if len(uservalidation.ValidateGroupName(j.Subjects[i].Name, false)) != 0 {
						j.Subjects[i].Name = fmt.Sprintf("validgroupname%d", i)
					}

				case authorizationapi.ServiceAccountKind:
					if len(validation.ValidateNamespaceName(j.Subjects[i].Namespace, false)) != 0 {
						j.Subjects[i].Namespace = fmt.Sprintf("sanamespacehere%d", i)
					}
					if len(validation.ValidateServiceAccountName(j.Subjects[i].Name, false)) != 0 {
						j.Subjects[i].Name = fmt.Sprintf("sanamehere%d", i)
					}

				case authorizationapi.SystemUserKind, authorizationapi.SystemGroupKind:
					j.Subjects[i].Namespace = ""
					j.Subjects[i].Name = ":" + j.Subjects[i].Name

				}

				j.Subjects[i].UID = types.UID("")
				j.Subjects[i].APIVersion = ""
				j.Subjects[i].ResourceVersion = ""
				j.Subjects[i].FieldPath = ""
			}
		},
		func(j *template.Template, c fuzz.Continue) {
			c.Fuzz(&j.ObjectMeta)
			c.Fuzz(&j.Parameters)
			// TODO: replace with structured type definition
			j.Objects = []runtime.Object{}
		},
		func(j *image.Image, c fuzz.Continue) {
			c.Fuzz(&j.ObjectMeta)
			c.Fuzz(&j.DockerImageMetadata)
			c.Fuzz(&j.Signatures)
			j.DockerImageMetadata.APIVersion = ""
			j.DockerImageMetadata.Kind = ""
			j.DockerImageMetadataVersion = []string{"pre012", "1.0"}[c.Rand.Intn(2)]
			j.DockerImageReference = c.RandString()
		},
		func(j *image.ImageSignature, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			j.Conditions = nil
			j.ImageIdentity = ""
			j.SignedClaims = nil
			j.Created = nil
			j.IssuedBy = nil
			j.IssuedTo = nil
		},
		func(j *image.ImageStream, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			for k, v := range j.Spec.Tags {
				if len(v.ReferencePolicy.Type) == 0 {
					v.ReferencePolicy.Type = image.SourceTagReferencePolicy
					j.Spec.Tags[k] = v
				}
			}
		},
		func(j *image.ImageStreamMapping, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			j.DockerImageRepository = ""
		},
		func(j *image.ImageImportSpec, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			if j.To == nil {
				// To is defaulted to be not nil
				j.To = &kapi.LocalObjectReference{}
			}
		},
		func(j *image.ImageStreamImage, c fuzz.Continue) {
			c.Fuzz(&j.Image)
			// because we de-embedded Image from ImageStreamImage, in order to round trip
			// successfully, the ImageStreamImage's ObjectMeta must match the Image's.
			j.ObjectMeta = j.Image.ObjectMeta
		},
		func(j *image.ImageStreamSpec, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			// if the generated fuzz value has a tag or image id, strip it
			if strings.ContainsAny(j.DockerImageRepository, ":@") {
				j.DockerImageRepository = ""
			}
			if j.Tags == nil {
				j.Tags = make(map[string]image.TagReference)
			}
			for k, v := range j.Tags {
				v.Name = k
				j.Tags[k] = v
			}
		},
		func(j *image.ImageStreamStatus, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			// if the generated fuzz value has a tag or image id, strip it
			if strings.ContainsAny(j.DockerImageRepository, ":@") {
				j.DockerImageRepository = ""
			}
		},
		func(j *image.ImageStreamTag, c fuzz.Continue) {
			c.Fuzz(&j.Image)
			// because we de-embedded Image from ImageStreamTag, in order to round trip
			// successfully, the ImageStreamTag's ObjectMeta must match the Image's.
			j.ObjectMeta = j.Image.ObjectMeta
		},
		func(j *image.TagReference, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			if j.From != nil {
				specs := []string{"", "ImageStreamTag", "ImageStreamImage"}
				j.From.Kind = specs[c.Intn(len(specs))]
			}
		},
		func(j *build.BuildConfigSpec, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			j.RunPolicy = build.BuildRunPolicySerial
		},
		func(j *build.SourceBuildStrategy, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			j.From.Kind = "ImageStreamTag"
			j.From.Name = "image:tag"
			j.From.APIVersion = ""
			j.From.ResourceVersion = ""
			j.From.FieldPath = ""
		},
		func(j *build.CustomBuildStrategy, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			j.From.Kind = "ImageStreamTag"
			j.From.Name = "image:tag"
			j.From.APIVersion = ""
			j.From.ResourceVersion = ""
			j.From.FieldPath = ""
		},
		func(j *build.DockerBuildStrategy, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			j.From.Kind = "ImageStreamTag"
			j.From.Name = "image:tag"
			j.From.APIVersion = ""
			j.From.ResourceVersion = ""
			j.From.FieldPath = ""
		},
		func(j *build.BuildOutput, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			if j.To != nil && (len(j.To.Kind) == 0 || j.To.Kind == "ImageStream") {
				j.To.Kind = "ImageStreamTag"
			}
			if j.To != nil && strings.Contains(j.To.Name, ":") {
				j.To.Name = strings.Replace(j.To.Name, ":", "-", -1)
			}
		},
		func(j *route.RouteTargetReference, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			j.Kind = "Service"
			j.Weight = new(int32)
			*j.Weight = 100
		},
		func(j *route.TLSConfig, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			if len(j.Termination) == 0 && len(j.DestinationCACertificate) == 0 {
				j.Termination = route.TLSTerminationEdge
			}
		},
		func(j *deploy.DeploymentConfig, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			j.Spec.Triggers = []deploy.DeploymentTriggerPolicy{{Type: deploy.DeploymentTriggerOnConfigChange}}
			if j.Spec.Template != nil && len(j.Spec.Template.Spec.Containers) == 1 {
				containerName := j.Spec.Template.Spec.Containers[0].Name
				if p := j.Spec.Strategy.RecreateParams; p != nil {
					defaultHookContainerName(p.Pre, containerName)
					defaultHookContainerName(p.Mid, containerName)
					defaultHookContainerName(p.Post, containerName)
				}
				if p := j.Spec.Strategy.RollingParams; p != nil {
					defaultHookContainerName(p.Pre, containerName)
					defaultHookContainerName(p.Post, containerName)
				}
			}
		},
		func(j *deploy.DeploymentStrategy, c fuzz.Continue) {
			randInt64 := func() *int64 {
				p := int64(c.RandUint64())
				return &p
			}
			c.FuzzNoCustom(j)
			j.RecreateParams, j.RollingParams, j.CustomParams = nil, nil, nil
			strategyTypes := []deploy.DeploymentStrategyType{deploy.DeploymentStrategyTypeRecreate, deploy.DeploymentStrategyTypeRolling, deploy.DeploymentStrategyTypeCustom}
			j.Type = strategyTypes[c.Rand.Intn(len(strategyTypes))]
			j.ActiveDeadlineSeconds = randInt64()
			switch j.Type {
			case deploy.DeploymentStrategyTypeRecreate:
				params := &deploy.RecreateDeploymentStrategyParams{}
				c.Fuzz(params)
				if params.TimeoutSeconds == nil {
					s := int64(120)
					params.TimeoutSeconds = &s
				}
				j.RecreateParams = params
			case deploy.DeploymentStrategyTypeRolling:
				params := &deploy.RollingDeploymentStrategyParams{}
				params.TimeoutSeconds = randInt64()
				params.IntervalSeconds = randInt64()
				params.UpdatePeriodSeconds = randInt64()

				policyTypes := []deploy.LifecycleHookFailurePolicy{
					deploy.LifecycleHookFailurePolicyRetry,
					deploy.LifecycleHookFailurePolicyAbort,
					deploy.LifecycleHookFailurePolicyIgnore,
				}
				if c.RandBool() {
					params.Pre = &deploy.LifecycleHook{
						FailurePolicy: policyTypes[c.Rand.Intn(len(policyTypes))],
						ExecNewPod: &deploy.ExecNewPodHook{
							ContainerName: c.RandString(),
						},
					}
				}
				if c.RandBool() {
					params.Post = &deploy.LifecycleHook{
						FailurePolicy: policyTypes[c.Rand.Intn(len(policyTypes))],
						ExecNewPod: &deploy.ExecNewPodHook{
							ContainerName: c.RandString(),
						},
					}
				}
				if c.RandBool() {
					params.MaxUnavailable = intstr.FromInt(int(c.RandUint64()))
					params.MaxSurge = intstr.FromInt(int(c.RandUint64()))
				} else {
					params.MaxSurge = intstr.FromString(fmt.Sprintf("%d%%", c.RandUint64()))
					params.MaxUnavailable = intstr.FromString(fmt.Sprintf("%d%%", c.RandUint64()))
				}
				j.RollingParams = params
			}
		},
		func(j *deploy.DeploymentCauseImageTrigger, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			specs := []string{"", "a/b", "a/b/c", "a:5000/b/c", "a/b", "a/b"}
			tags := []string{"stuff", "other"}
			j.From.Name = specs[c.Intn(len(specs))]
			if len(j.From.Name) > 0 {
				j.From.Name = image.JoinImageStreamTag(j.From.Name, tags[c.Intn(len(tags))])
			}
		},
		func(j *deploy.DeploymentTriggerImageChangeParams, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			specs := []string{"a/b", "a/b/c", "a:5000/b/c", "a/b:latest", "a/b@test"}
			j.From.Kind = "DockerImage"
			j.From.Name = specs[c.Intn(len(specs))]
		},

		// TODO: uncomment when round tripping for init containers is available (the annotation is
		// not supported on security context review for now)
		func(j *securityapi.PodSecurityPolicyReview, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			j.Spec.Template.Spec.InitContainers = nil
			for i := range j.Status.AllowedServiceAccounts {
				j.Status.AllowedServiceAccounts[i].Template.Spec.InitContainers = nil
			}
		},
		func(j *securityapi.PodSecurityPolicySelfSubjectReview, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			j.Spec.Template.Spec.InitContainers = nil
			j.Status.Template.Spec.InitContainers = nil
		},
		func(j *securityapi.PodSecurityPolicySubjectReview, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			j.Spec.Template.Spec.InitContainers = nil
			j.Status.Template.Spec.InitContainers = nil
		},
		func(j *oauthapi.OAuthAuthorizeToken, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			if len(j.CodeChallenge) > 0 && len(j.CodeChallengeMethod) == 0 {
				j.CodeChallengeMethod = "plain"
			}
		},
		func(j *oauthapi.OAuthClientAuthorization, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			if len(j.Scopes) == 0 {
				j.Scopes = append(j.Scopes, "user:full")
			}
		},
		func(j *route.RouteSpec, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			if len(j.WildcardPolicy) == 0 {
				j.WildcardPolicy = route.WildcardPolicyNone
			}
		},
		func(j *route.RouteIngress, c fuzz.Continue) {
			c.FuzzNoCustom(j)
			if len(j.WildcardPolicy) == 0 {
				j.WildcardPolicy = route.WildcardPolicyNone
			}
		},

		func(j *runtime.Object, c fuzz.Continue) {
			// runtime.EmbeddedObject causes a panic inside of fuzz because runtime.Object isn't handled.
		},
		func(t *time.Time, c fuzz.Continue) {
			// This is necessary because the standard fuzzed time.Time object is
			// completely nil, but when JSON unmarshals dates it fills in the
			// unexported loc field with the time.UTC object, resulting in
			// reflect.DeepEqual returning false in the round trip tests. We solve it
			// by using a date that will be identical to the one JSON unmarshals.
			*t = time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC)
		},
		func(u64 *uint64, c fuzz.Continue) {
			// TODO: uint64's are NOT handled right.
			*u64 = c.RandUint64() >> 8
		},
	)

	f.Fuzz(item)

	j, err := meta.TypeAccessor(item)
	if err != nil {
		t.Fatalf("Unexpected error %v for %#v", err, item)
	}
	j.SetKind("")
	j.SetAPIVersion("")

	return item
}

func defaultHookContainerName(hook *deploy.LifecycleHook, containerName string) {
	if hook == nil {
		return
	}
	for i := range hook.TagImages {
		if len(hook.TagImages[i].ContainerName) == 0 {
			hook.TagImages[i].ContainerName = containerName
		}
	}
	if hook.ExecNewPod != nil {
		if len(hook.ExecNewPod.ContainerName) == 0 {
			hook.ExecNewPod.ContainerName = containerName
		}
	}
}

func roundTripWithAllCodecs(t *testing.T, version unversioned.GroupVersion, item runtime.Object) {
	var codecs []runtime.Codec
	for _, fn := range codecsToTest {
		codec, err := fn(version, item)
		if err != nil {
			t.Errorf("unable to get codec: %v", err)
			return
		}
		codecs = append(codecs, codec)
	}
	for _, codec := range codecs {
		roundTrip(t, codec, item)
	}
}

func roundTrip(t *testing.T, codec runtime.Codec, originalItem runtime.Object) {
	// Make a copy of the originalItem to give to conversion functions
	// This lets us know if conversion messed with the input object
	deepCopy, err := kapi.Scheme.DeepCopy(originalItem)
	if err != nil {
		t.Errorf("Could not copy object: %v", err)
		return
	}
	item := deepCopy.(runtime.Object)

	name := reflect.TypeOf(item).Elem().Name()
	data, err := runtime.Encode(codec, item)
	if err != nil {
		if runtime.IsNotRegisteredError(err) {
			t.Logf("%v skipped: not registered: %v", name, err)
			return
		}
		t.Errorf("%v: %v (%#v)", name, err, item)
		return
	}

	obj2, err := runtime.Decode(codec, data)
	if err != nil {
		t.Errorf("0: %v: %v\nCodec: %v\nData: %s\nSource: %#v", name, err, codec, string(data), originalItem)
		return
	}
	if reflect.TypeOf(item) != reflect.TypeOf(obj2) {
		obj2conv := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
		if err := kapi.Scheme.Convert(obj2, obj2conv, nil); err != nil {
			t.Errorf("0X: no conversion from %v to %v: %v", reflect.TypeOf(item), reflect.TypeOf(obj2), err)
			return
		}
		obj2 = obj2conv
	}

	if !kapi.Semantic.DeepEqual(originalItem, obj2) {
		t.Errorf("1: %v: diff: %v\nCodec: %v\nData: %s", name, diff.ObjectReflectDiff(originalItem, obj2), codec, dataToString(data))
		return
	}

	obj3 := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
	if err := runtime.DecodeInto(codec, data, obj3); err != nil {
		t.Errorf("2: %v: %v", name, err)
		return
	}
	if !kapi.Semantic.DeepEqual(originalItem, obj3) {
		t.Errorf("3: %v: diff: %v\nCodec: %v", name, diff.ObjectReflectDiff(originalItem, obj3), codec)
		return
	}
}

func dataToString(s []byte) string {
	if bytes.HasPrefix(s, []byte("k8s")) {
		return "\n" + hex.Dump(s)
	}
	return string(s)
}

// skipStandardVersions is a map of Kind to a list of API versions to test with.
var skipStandardVersions = map[string][]unversioned.GroupVersion{
	// The API versions here are to test our object that serializes from/into
	// docker's registry API.
	"DockerImage": {dockerpre012.SchemeGroupVersion, docker10.SchemeGroupVersion},
}

const fuzzIters = 20

// For debugging problems
func TestSpecificKind(t *testing.T) {
	kapi.Scheme.Log(t)
	defer kapi.Scheme.Log(nil)

	kind := "ClusterRole"
	item, err := kapi.Scheme.New(osapi.SchemeGroupVersion.WithKind(kind))
	if err != nil {
		t.Fatalf("Couldn't make a %v? %v", kind, err)
	}
	codec, err := codecsToTest[1](v1.SchemeGroupVersion, nil)
	if err != nil {
		t.Fatal(err)
	}
	seed := int64(2703387474910584091)
	for i := 0; i < fuzzIters; i++ {
		//t.Logf(`About to test %v with "v1"`, kind)
		fuzzInternalObject(t, v1.SchemeGroupVersion, item, seed)
		roundTrip(t, codec, item)
	}
}

// Keep this in sync with the respective upstream set
// WatchEvent does not have TypeMeta and cannot be roundtripped.
var nonInternalRoundTrippableTypes = sets.NewString("List", "ListOptions", "WatchEvent")

// TestTypes will try to roundtrip all OpenShift and Kubernetes stable api types
func TestTypes(t *testing.T) {
	internalVersionToExternalVersions := map[unversioned.GroupVersion][]unversioned.GroupVersion{
		osapi.SchemeGroupVersion:    {v1.SchemeGroupVersion},
		quotaapi.SchemeGroupVersion: {quotaapiv1.SchemeGroupVersion},
	}

	for internalVersion, externalVersions := range internalVersionToExternalVersions {
		for kind, reflectType := range kapi.Scheme.KnownTypes(internalVersion) {
			if !strings.Contains(reflectType.PkgPath(), "github.com/openshift/origin/") && reflectType.PkgPath() != "github.com/openshift/origin/vendor/k8s.io/kubernetes/pkg/api" {
				continue
			}
			if nonInternalRoundTrippableTypes.Has(kind) {
				continue
			}

			for _, externalVersion := range externalVersions {
				// Try a few times, since runTest uses random values.
				for i := 0; i < fuzzIters; i++ {
					item, err := kapi.Scheme.New(internalVersion.WithKind(kind))
					if err != nil {
						t.Errorf("Couldn't make a %v? %v", kind, err)
						continue
					}
					if _, err := meta.TypeAccessor(item); err != nil {
						t.Fatalf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableTypes: %v", kind, err)
					}
					seed := rand.Int63()

					if versions, ok := skipStandardVersions[kind]; ok {
						for _, v := range versions {
							fuzzInternalObject(t, v, item, seed)
							roundTrip(t, kapi.Codecs.LegacyCodec(v), item)
						}
						continue
					}
					fuzzInternalObject(t, externalVersion, item, seed)
					roundTripWithAllCodecs(t, externalVersion, item)
				}
			}
		}

	}
}