Repository: knative/pkg
Pull request #1317 (2020-05-09 04:24:24)

add Status.Annotations field
https://docs.google.com/document/d/1LFE4IwQ7yj6zjO7qwMSXDRfPk8ll9hSk6uCh2Xx--AE/edit#

knative-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/pkg/pull/1317#" title="Author self-approved">vaikas</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fpkg).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/knative/pkg/blob/master/OWNERS)~~ [vaikas]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES
index 029964e4d..35fc90205 100644
--- a/OWNERS_ALIASES
+++ b/OWNERS_ALIASES
@@ -24,7 +24,6 @@ aliases:
   - vagababov
 
   controller-approvers:
-  - dgerd
   - grantr
   - mattmoor
   - tcnghia
@@ -32,7 +31,6 @@ aliases:
 
   kmeta-approvers:
   - mattmoor
-  - dgerd
   - vagababov
 
   logging-approvers:
diff --git a/apis/duck/v1/kresource_type.go b/apis/duck/v1/kresource_type.go
index 80558428c..78d8ece66 100644
--- a/apis/duck/v1/kresource_type.go
+++ b/apis/duck/v1/kresource_type.go
@@ -22,15 +22,15 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 
 	"knative.dev/pkg/apis"
 )
 
 // KRShaped is an interface for retrieving the duck elements of an arbitrary resource.
 type KRShaped interface {
-	metav1.ObjectMetaAccessor
-
-	GetTypeMeta() *metav1.TypeMeta
+	metav1.Object
+	schema.ObjectKind
 
 	GetStatus() *Status
 
@@ -81,11 +81,6 @@ type KResourceList struct {
 	Items []KResource `json:"items"`
 }
 
-// GetTypeMeta retrieves the ObjectMeta of the KResource. Implements the KRShaped interface.
-func (t *KResource) GetTypeMeta() *metav1.TypeMeta {
-	return &t.TypeMeta
-}
-
 // GetStatus retrieves the status of the KResource. Implements the KRShaped interface.
 func (t *KResource) GetStatus() *Status {
 	return &t.Status
diff --git a/apis/duck/v1/status_types.go b/apis/duck/v1/status_types.go
index 9186e961f..365cc5edf 100644
--- a/apis/duck/v1/status_types.go
+++ b/apis/duck/v1/status_types.go
@@ -45,6 +45,12 @@ type Status struct {
 	// +patchMergeKey=type
 	// +patchStrategy=merge
 	Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
+
+	// Annotations is additional Status fields for the Resource to save some
+	// additional State as well as convey more information to the user. This is
+	// roughly akin to Annotations on any k8s resource, just the reconciler conveying
+	// richer information outwards.
+	Annotations map[string]string `json:"annotations,omitempty"`
 }
 
 var _ apis.ConditionsAccessor = (*Status)(nil)
diff --git a/apis/duck/v1/zz_generated.deepcopy.go b/apis/duck/v1/zz_generated.deepcopy.go
index 3397713be..190d773e7 100644
--- a/apis/duck/v1/zz_generated.deepcopy.go
+++ b/apis/duck/v1/zz_generated.deepcopy.go
@@ -428,6 +428,13 @@ func (in *Status) DeepCopyInto(out *Status) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	if in.Annotations != nil {
+		in, out := &in.Annotations, &out.Annotations
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
 	return
 }
 
diff --git a/apis/duck/v1beta1/status_types.go b/apis/duck/v1beta1/status_types.go
index b2095fb58..a046dbedf 100644
--- a/apis/duck/v1beta1/status_types.go
+++ b/apis/duck/v1beta1/status_types.go
@@ -63,6 +63,12 @@ type Status struct {
 	// +patchMergeKey=type
 	// +patchStrategy=merge
 	Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
+
+	// Annotations is additional Status fields for the Resource to save some
+	// additional State as well as convey more information to the user. This is
+	// roughly akin to Annotations on any k8s resource, just the reconciler conveying
+	// richer information outwards.
+	Annotations map[string]string `json:"annotations,omitempty"`
 }
 
 var _ apis.ConditionsAccessor = (*Status)(nil)
diff --git a/apis/duck/v1beta1/zz_generated.deepcopy.go b/apis/duck/v1beta1/zz_generated.deepcopy.go
index 032dccb3c..152bb8828 100644
--- a/apis/duck/v1beta1/zz_generated.deepcopy.go
+++ b/apis/duck/v1beta1/zz_generated.deepcopy.go
@@ -374,6 +374,13 @@ func (in *Status) DeepCopyInto(out *Status) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	if in.Annotations != nil {
+		in, out := &in.Annotations, &out.Annotations
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
 	return
 }
 
diff --git a/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/reconciler.go b/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/reconciler.go
index 5e9b40921..d2c832ed1 100644
--- a/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/reconciler.go
+++ b/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/reconciler.go
@@ -167,6 +167,7 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 		// Reconcile this copy of the resource and then write back any status
 		// updates regardless of whether the reconciliation errored out.
 		reconcileEvent = r.reconciler.ReconcileKind(ctx, resource)
+
 	} else if fin, ok := r.reconciler.(Finalizer); ok {
 		// Append the target method to the logger.
 		logger = logger.With(zap.String("targetMethod", "FinalizeKind"))
@@ -198,13 +199,19 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 		if reconciler.EventAs(reconcileEvent, &event) {
 			logger.Infow("Returned an event", zap.Any("event", reconcileEvent))
 			r.Recorder.Eventf(resource, event.EventType, event.Reason, event.Format, event.Args...)
+
+			// the event was wrapped inside an error, consider the reconciliation as failed
+			if _, isEvent := reconcileEvent.(*reconciler.ReconcilerEvent); !isEvent {
+				return reconcileEvent
+			}
 			return nil
-		} else {
-			logger.Errorw("Returned an error", zap.Error(reconcileEvent))
-			r.Recorder.Event(resource, v1.EventTypeWarning, "InternalError", reconcileEvent.Error())
-			return reconcileEvent
 		}
+
+		logger.Errorw("Returned an error", zap.Error(reconcileEvent))
+		r.Recorder.Event(resource, v1.EventTypeWarning, "InternalError", reconcileEvent.Error())
+		return reconcileEvent
 	}
+
 	return nil
 }
 
diff --git a/client/injection/kube/reconciler/core/v1/namespace/reconciler.go b/client/injection/kube/reconciler/core/v1/namespace/reconciler.go
index 10bbab032..f040f5681 100644
--- a/client/injection/kube/reconciler/core/v1/namespace/reconciler.go
+++ b/client/injection/kube/reconciler/core/v1/namespace/reconciler.go
@@ -166,6 +166,7 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 		// Reconcile this copy of the resource and then write back any status
 		// updates regardless of whether the reconciliation errored out.
 		reconcileEvent = r.reconciler.ReconcileKind(ctx, resource)
+
 	} else if fin, ok := r.reconciler.(Finalizer); ok {
 		// Append the target method to the logger.
 		logger = logger.With(zap.String("targetMethod", "FinalizeKind"))
@@ -197,13 +198,19 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 		if reconciler.EventAs(reconcileEvent, &event) {
 			logger.Infow("Returned an event", zap.Any("event", reconcileEvent))
 			r.Recorder.Eventf(resource, event.EventType, event.Reason, event.Format, event.Args...)
+
+			// the event was wrapped inside an error, consider the reconciliation as failed
+			if _, isEvent := reconcileEvent.(*reconciler.ReconcilerEvent); !isEvent {
+				return reconcileEvent
+			}
 			return nil
-		} else {
-			logger.Errorw("Returned an error", zap.Error(reconcileEvent))
-			r.Recorder.Event(resource, v1.EventTypeWarning, "InternalError", reconcileEvent.Error())
-			return reconcileEvent
 		}
+
+		logger.Errorw("Returned an error", zap.Error(reconcileEvent))
+		r.Recorder.Event(resource, v1.EventTypeWarning, "InternalError", reconcileEvent.Error())
+		return reconcileEvent
 	}
+
 	return nil
 }
 
diff --git a/codegen/cmd/injection-gen/generators/packages.go b/codegen/cmd/injection-gen/generators/packages.go
index 287257069..c54d6f915 100644
--- a/codegen/cmd/injection-gen/generators/packages.go
+++ b/codegen/cmd/injection-gen/generators/packages.go
@@ -173,17 +173,11 @@ func MustParseClientGenTags(lines []string) Tags {
 		Tags: util.MustParseClientGenTags(lines),
 	}
 
-	values := types.ExtractCommentTags("+", lines)
+	values := ExtractCommentTags("+", lines)
 
 	_, ret.GenerateDuck = values["genduck"]
 
-	_, genRec := values["genreconciler"]
-	_, genRecClass := values["genreconciler:class"]
-
-	// Generate Reconciler code if genreconciler OR genreconciler:class exist.
-	if genRec || genRecClass {
-		ret.GenerateReconciler = true
-	}
+	_, ret.GenerateReconciler = values["genreconciler"]
 
 	return ret
 }
@@ -202,22 +196,21 @@ func extractReconcilerClassTag(tags map[string]map[string]string) (string, bool)
 	return classname, has
 }
 
-func isNonNamespaced(tags map[string]map[string]string) bool {
-	vals, has := tags["genclient"]
+func isKRShaped(tags map[string]map[string]string) bool {
+	vals, has := tags["genreconciler"]
 	if !has {
 		return false
 	}
-	_, has = vals["nonNamespaced"]
-	return has
+	return vals["krshapedlogic"] == "true"
 }
 
-func isKRShaped(tags map[string]map[string]string) bool {
+func isNonNamespaced(tags map[string]map[string]string) bool {
 	vals, has := tags["genclient"]
 	if !has {
 		return false
 	}
-	shaped, _ := vals["krshapedlogic"]
-	return shaped == "true"
+	_, has = vals["nonNamespaced"]
+	return has
 }
 
 func vendorless(p string) string {
@@ -234,118 +227,108 @@ func typedInformerPackage(groupPkgName string, gv clientgentypes.GroupVersion, e
 func versionClientsPackages(basePackage string, boilerplate []byte, customArgs *informergenargs.CustomArgs) []generator.Package {
 	packagePath := filepath.Join(basePackage, "client")
 
-	vers := make([]generator.Package, 0, 2)
-
-	// Impl
-	vers = append(vers, &generator.DefaultPackage{
-		PackageName: "client",
-		PackagePath: packagePath,
-		HeaderText:  boilerplate,
-		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
-			// Impl
-			generators = append(generators, &clientGenerator{
-				DefaultGen: generator.DefaultGen{
-					OptionalName: "client",
-				},
-				outputPackage:    packagePath,
-				imports:          generator.NewImportTracker(),
-				clientSetPackage: customArgs.VersionedClientSetPackage,
-			})
-
-			return generators
-		},
-		FilterFunc: func(c *generator.Context, t *types.Type) bool {
-			tags := MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
-			return tags.NeedsInformerInjection()
-		},
-	})
-
-	// Fake
-	vers = append(vers, &generator.DefaultPackage{
-		PackageName: "fake",
-		PackagePath: filepath.Join(packagePath, "fake"),
-		HeaderText:  boilerplate,
-		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
-
-			// Impl
-			generators = append(generators, &fakeClientGenerator{
-				DefaultGen: generator.DefaultGen{
-					OptionalName: "fake",
-				},
-				outputPackage:      filepath.Join(packagePath, "fake"),
-				imports:            generator.NewImportTracker(),
-				fakeClientPkg:      filepath.Join(customArgs.VersionedClientSetPackage, "fake"),
-				clientInjectionPkg: packagePath,
-			})
-
-			return generators
+	vers := []generator.Package{
+		// Impl
+		&generator.DefaultPackage{
+			PackageName: "client",
+			PackagePath: packagePath,
+			HeaderText:  boilerplate,
+			GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
+				// Impl
+				generators = append(generators, &clientGenerator{
+					DefaultGen: generator.DefaultGen{
+						OptionalName: "client",
+					},
+					outputPackage:    packagePath,
+					imports:          generator.NewImportTracker(),
+					clientSetPackage: customArgs.VersionedClientSetPackage,
+				})
+				return generators
+			},
+			FilterFunc: func(c *generator.Context, t *types.Type) bool {
+				tags := MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
+				return tags.NeedsInformerInjection()
+			},
 		},
-		FilterFunc: func(c *generator.Context, t *types.Type) bool {
-			tags := MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
-			return tags.NeedsInformerInjection()
+		// Fake
+		&generator.DefaultPackage{
+			PackageName: "fake",
+			PackagePath: filepath.Join(packagePath, "fake"),
+			HeaderText:  boilerplate,
+			GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
+				// Impl
+				generators = append(generators, &fakeClientGenerator{
+					DefaultGen: generator.DefaultGen{
+						OptionalName: "fake",
+					},
+					outputPackage:      filepath.Join(packagePath, "fake"),
+					imports:            generator.NewImportTracker(),
+					fakeClientPkg:      filepath.Join(customArgs.VersionedClientSetPackage, "fake"),
+					clientInjectionPkg: packagePath,
+				})
+				return generators
+			},
+			FilterFunc: func(c *generator.Context, t *types.Type) bool {
+				tags := MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
+				return tags.NeedsInformerInjection()
+			},
 		},
-	})
-
+	}
 	return vers
 }
 
 func versionFactoryPackages(basePackage string, boilerplate []byte, customArgs *informergenargs.CustomArgs) []generator.Package {
 	packagePath := filepath.Join(basePackage, "informers", "factory")
 
-	vers := make([]generator.Package, 0, 2)
-
-	// Impl
-	vers = append(vers, &generator.DefaultPackage{
-		PackageName: "factory",
-		PackagePath: packagePath,
-		HeaderText:  boilerplate,
-		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
-			// Impl
-			generators = append(generators, &factoryGenerator{
-				DefaultGen: generator.DefaultGen{
-					OptionalName: "factory",
-				},
-				outputPackage:                packagePath,
-				cachingClientSetPackage:      filepath.Join(basePackage, "client"),
-				sharedInformerFactoryPackage: customArgs.ExternalVersionsInformersPackage,
-				imports:                      generator.NewImportTracker(),
-			})
-
-			return generators
-		},
-		FilterFunc: func(c *generator.Context, t *types.Type) bool {
-			tags := MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
-			return tags.NeedsInformerInjection()
-		},
-	})
-
-	// Fake
-	vers = append(vers, &generator.DefaultPackage{
-		PackageName: "fake",
-		PackagePath: filepath.Join(packagePath, "fake"),
-		HeaderText:  boilerplate,
-		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
-
-			// Impl
-			generators = append(generators, &fakeFactoryGenerator{
-				DefaultGen: generator.DefaultGen{
-					OptionalName: "fake",
-				},
-				outputPackage:                filepath.Join(packagePath, "fake"),
-				factoryInjectionPkg:          packagePath,
-				fakeClientInjectionPkg:       filepath.Join(basePackage, "client", "fake"),
-				sharedInformerFactoryPackage: customArgs.ExternalVersionsInformersPackage,
-				imports:                      generator.NewImportTracker(),
-			})
-
-			return generators
+	vers := []generator.Package{
+		// Impl
+		&generator.DefaultPackage{
+			PackageName: "factory",
+			PackagePath: packagePath,
+			HeaderText:  boilerplate,
+			GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
+				// Impl
+				generators = append(generators, &factoryGenerator{
+					DefaultGen: generator.DefaultGen{
+						OptionalName: "factory",
+					},
+					outputPackage:                packagePath,
+					cachingClientSetPackage:      filepath.Join(basePackage, "client"),
+					sharedInformerFactoryPackage: customArgs.ExternalVersionsInformersPackage,
+					imports:                      generator.NewImportTracker(),
+				})
+				return generators
+			},
+			FilterFunc: func(c *generator.Context, t *types.Type) bool {
+				tags := MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
+				return tags.NeedsInformerInjection()
+			},
 		},
-		FilterFunc: func(c *generator.Context, t *types.Type) bool {
-			tags := MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
-			return tags.NeedsInformerInjection()
+		// Fake
+		&generator.DefaultPackage{
+			PackageName: "fake",
+			PackagePath: filepath.Join(packagePath, "fake"),
+			HeaderText:  boilerplate,
+			GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
+				// Impl
+				generators = append(generators, &fakeFactoryGenerator{
+					DefaultGen: generator.DefaultGen{
+						OptionalName: "fake",
+					},
+					outputPackage:                filepath.Join(packagePath, "fake"),
+					factoryInjectionPkg:          packagePath,
+					fakeClientInjectionPkg:       filepath.Join(basePackage, "client", "fake"),
+					sharedInformerFactoryPackage: customArgs.ExternalVersionsInformersPackage,
+					imports:                      generator.NewImportTracker(),
+				})
+				return generators
+			},
+			FilterFunc: func(c *generator.Context, t *types.Type) bool {
+				tags := MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
+				return tags.NeedsInformerInjection()
+			},
 		},
-	})
-
+	}
 	return vers
 }
 
@@ -353,12 +336,11 @@ func versionInformerPackages(basePackage string, groupPkgName string, gv clientg
 	factoryPackagePath := filepath.Join(basePackage, "informers", "factory")
 	packagePath := filepath.Join(basePackage, "informers", groupPkgName, strings.ToLower(gv.Version.NonEmpty()))
 
-	vers := make([]generator.Package, 0, len(typesToGenerate))
+	vers := make([]generator.Package, 0, 2*len(typesToGenerate))
 
 	for _, t := range typesToGenerate {
 		// Fix for golang iterator bug.
 		t := t
-
 		packagePath := packagePath + "/" + strings.ToLower(t.Name.Name)
 		typedInformerPackage := typedInformerPackage(groupPkgName, gv, customArgs.ExternalVersionsInformersPackage)
 
@@ -381,7 +363,6 @@ func versionInformerPackages(basePackage string, groupPkgName string, gv clientg
 					typedInformerPackage:        typedInformerPackage,
 					groupInformerFactoryPackage: factoryPackagePath,
 				})
-
 				return generators
 			},
 			FilterFunc: func(c *generator.Context, t *types.Type) bool {
@@ -409,7 +390,6 @@ func versionInformerPackages(basePackage string, groupPkgName string, gv clientg
 					informerInjectionPkg:    packagePath,
 					fakeFactoryInjectionPkg: filepath.Join(factoryPackagePath, "fake"),
 				})
-
 				return generators
 			},
 			FilterFunc: func(c *generator.Context, t *types.Type) bool {
@@ -423,13 +403,13 @@ func versionInformerPackages(basePackage string, groupPkgName string, gv clientg
 
 func reconcilerPackages(basePackage string, groupPkgName string, gv clientgentypes.GroupVersion, groupGoName string, boilerplate []byte, typesToGenerate []*types.Type, customArgs *informergenargs.CustomArgs) []generator.Package {
 	packagePath := filepath.Join(basePackage, "reconciler", groupPkgName, strings.ToLower(gv.Version.NonEmpty()))
+	clientPackagePath := filepath.Join(basePackage, "client")
 
-	vers := make([]generator.Package, 0, len(typesToGenerate))
+	vers := make([]generator.Package, 0, 4*len(typesToGenerate))
 
 	for _, t := range typesToGenerate {
 		// Fix for golang iterator bug.
 		t := t
-
 		extracted := extractCommentTags(t)
 		reconcilerClass, hasReconcilerClass := extractReconcilerClassTag(extracted)
 		nonNamespaced := isNonNamespaced(extracted)
@@ -437,9 +417,7 @@ func reconcilerPackages(basePackage string, groupPkgName string, gv clientgentyp
 
 		packagePath := filepath.Join(packagePath, strings.ToLower(t.Name.Name))
 
-		clientPackagePath := filepath.Join(basePackage, "client")
 		informerPackagePath := filepath.Join(basePackage, "informers", groupPkgName, strings.ToLower(gv.Version.NonEmpty()), strings.ToLower(t.Name.Name))
-
 		listerPackagePath := filepath.Join(customArgs.ListersPackage, groupPkgName, strings.ToLower(gv.Version.NonEmpty()))
 
 		// Controller
@@ -463,7 +441,6 @@ func reconcilerPackages(basePackage string, groupPkgName string, gv clientgentyp
 					reconcilerClass:     reconcilerClass,
 					hasReconcilerClass:  hasReconcilerClass,
 				})
-
 				return generators
 			},
 			FilterFunc: func(c *generator.Context, t *types.Type) bool {
@@ -491,7 +468,6 @@ func reconcilerPackages(basePackage string, groupPkgName string, gv clientgentyp
 					reconcilerClass:     reconcilerClass,
 					hasReconcilerClass:  hasReconcilerClass,
 				})
-
 				return generators
 			},
 			FilterFunc: func(c *generator.Context, t *types.Type) bool {
@@ -524,7 +500,6 @@ func reconcilerPackages(basePackage string, groupPkgName string, gv clientgentyp
 					nonNamespaced:      nonNamespaced,
 					isKRShaped:         isKRShaped,
 				})
-
 				return generators
 			},
 			FilterFunc: func(c *generator.Context, t *types.Type) bool {
@@ -549,7 +524,6 @@ func reconcilerPackages(basePackage string, groupPkgName string, gv clientgentyp
 					outputPackage:  filepath.Join(packagePath, "stub"),
 					imports:        generator.NewImportTracker(),
 				})
-
 				return generators
 			},
 			FilterFunc: func(c *generator.Context, t *types.Type) bool {
@@ -564,12 +538,11 @@ func reconcilerPackages(basePackage string, groupPkgName string, gv clientgentyp
 func versionDuckPackages(basePackage string, groupPkgName string, gv clientgentypes.GroupVersion, groupGoName string, boilerplate []byte, typesToGenerate []*types.Type, customArgs *informergenargs.CustomArgs) []generator.Package {
 	packagePath := filepath.Join(basePackage, "ducks", groupPkgName, strings.ToLower(gv.Version.NonEmpty()))
 
-	vers := make([]generator.Package, 0, len(typesToGenerate))
+	vers := make([]generator.Package, 0, 2*len(typesToGenerate))
 
 	for _, t := range typesToGenerate {
 		// Fix for golang iterator bug.
 		t := t
-
 		packagePath := filepath.Join(packagePath, strings.ToLower(t.Name.Name))
 
 		// Impl
@@ -589,7 +562,6 @@ func versionDuckPackages(basePackage string, groupPkgName string, gv clientgenty
 					typeToGenerate: t,
 					imports:        generator.NewImportTracker(),
 				})
-
 				return generators
 			},
 			FilterFunc: func(c *generator.Context, t *types.Type) bool {
@@ -616,7 +588,6 @@ func versionDuckPackages(basePackage string, groupPkgName string, gv clientgenty
 					groupGoName:      groupGoName,
 					duckInjectionPkg: packagePath,
 				})
-
 				return generators
 			},
 			FilterFunc: func(c *generator.Context, t *types.Type) bool {
diff --git a/configmap/parse.go b/configmap/parse.go
new file mode 100644
index 000000000..6479598b7
--- /dev/null
+++ b/configmap/parse.go
@@ -0,0 +1,125 @@
+/*
+Copyright 2020 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 configmap
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+
+	"k8s.io/apimachinery/pkg/util/sets"
+)
+
+// ParseFunc is a function taking ConfigMap data and applying a parse operation to it.
+type ParseFunc func(map[string]string) error
+
+// AsString passes the value at key through into the target, if it exists.
+func AsString(key string, target *string) ParseFunc {
+	return func(data map[string]string) error {
+		if raw, ok := data[key]; ok {
+			*target = raw
+		}
+		return nil
+	}
+}
+
+// AsBool parses the value at key as a boolean into the target, if it exists.
+func AsBool(key string, target *bool) ParseFunc {
+	return func(data map[string]string) error {
+		if raw, ok := data[key]; ok {
+			*target = strings.EqualFold(raw, "true")
+		}
+		return nil
+	}
+}
+
+// AsInt32 parses the value at key as an int32 into the target, if it exists.
+func AsInt32(key string, target *int32) ParseFunc {
+	return func(data map[string]string) error {
+		if raw, ok := data[key]; ok {
+			val, err := strconv.ParseInt(raw, 10, 32)
+			if err != nil {
+				return fmt.Errorf("failed to parse %q: %w", key, err)
+			}
+			*target = int32(val)
+		}
+		return nil
+	}
+}
+
+// AsInt64 parses the value at key as an int64 into the target, if it exists.
+func AsInt64(key string, target *int64) ParseFunc {
+	return func(data map[string]string) error {
+		if raw, ok := data[key]; ok {
+			val, err := strconv.ParseInt(raw, 10, 64)
+			if err != nil {
+				return fmt.Errorf("failed to parse %q: %w", key, err)
+			}
+			*target = val
+		}
+		return nil
+	}
+}
+
+// AsFloat64 parses the value at key as a float64 into the target, if it exists.
+func AsFloat64(key string, target *float64) ParseFunc {
+	return func(data map[string]string) error {
+		if raw, ok := data[key]; ok {
+			val, err := strconv.ParseFloat(raw, 64)
+			if err != nil {
+				return fmt.Errorf("failed to parse %q: %w", key, err)
+			}
+			*target = val
+		}
+		return nil
+	}
+}
+
+// AsDuration parses the value at key as a time.Duration into the target, if it exists.
+func AsDuration(key string, target *time.Duration) ParseFunc {
+	return func(data map[string]string) error {
+		if raw, ok := data[key]; ok {
+			val, err := time.ParseDuration(raw)
+			if err != nil {
+				return fmt.Errorf("failed to parse %q: %w", key, err)
+			}
+			*target = val
+		}
+		return nil
+	}
+}
+
+// AsStringSet parses the value at key as a sets.String (split by ',') into the target, if it exists.
+func AsStringSet(key string, target *sets.String) ParseFunc {
+	return func(data map[string]string) error {
+		if raw, ok := data[key]; ok {
+			*target = sets.NewString(strings.Split(raw, ",")...)
+		}
+		return nil
+	}
+}
+
+// Parse parses the given map using the parser functions passed in.
+func Parse(data map[string]string, parsers ...ParseFunc) error {
+	for _, parse := range parsers {
+		if err := parse(data); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/go.mod b/go.mod
index f90393f0f..e4baf0641 100644
--- a/go.mod
+++ b/go.mod
@@ -48,7 +48,7 @@ require (
 	k8s.io/klog v1.0.0
 	k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
 	k8s.io/test-infra v0.0.0-20200407001919-bc7f71ef65b8
-	knative.dev/test-infra v0.0.0-20200508015845-8d7d46a46176
+	knative.dev/test-infra v0.0.0-20200519015156-82551620b0a9
 )
 
 replace (
diff --git a/go.sum b/go.sum
index 98ad2959b..80335d18d 100644
--- a/go.sum
+++ b/go.sum
@@ -1348,10 +1348,13 @@ knative.dev/pkg v0.0.0-20191101194912-56c2594e4f11/go.mod h1:pgODObA1dTyhNoFxPZT
 knative.dev/pkg v0.0.0-20191111150521-6d806b998379/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q=
 knative.dev/pkg v0.0.0-20200428194351-90fc61bae7f7/go.mod h1:o+e8OVEJKIuvXPsGVPIautjXgs05xbos7G+QMRjuUps=
 knative.dev/pkg v0.0.0-20200505191044-3da93ebb24c2/go.mod h1:Q6sL35DdGs8hIQZKdaCXJGgY8f90BmNBKSb8z6d/BTM=
+knative.dev/pkg v0.0.0-20200515002500-16d7b963416f/go.mod h1:tMOHGbxtRz8zYFGEGpV/bpoTEM1o89MwYFC4YJXl3GY=
 knative.dev/test-infra v0.0.0-20200407185800-1b88cb3b45a5/go.mod h1:xcdUkMJrLlBswIZqL5zCuBFOC22WIPMQoVX1L35i0vQ=
 knative.dev/test-infra v0.0.0-20200505052144-5ea2f705bb55/go.mod h1:WqF1Azka+FxPZ20keR2zCNtiQA1MP9ZB4BH4HuI+SIU=
-knative.dev/test-infra v0.0.0-20200508015845-8d7d46a46176 h1:Uab7/fRKm5lujDe+DorXGTA1v5ACzZ1wUwhcIvqcNjI=
-knative.dev/test-infra v0.0.0-20200508015845-8d7d46a46176/go.mod h1:aMif0KXL4g19YCYwsy4Ocjjz5xgPlseYV+B95Oo4JGE=
+knative.dev/test-infra v0.0.0-20200513011557-d03429a76034 h1:JxqONCZVS7or+Fv3ebVQoipuIBH7Ig3Qbx170hgIF+A=
+knative.dev/test-infra v0.0.0-20200513011557-d03429a76034/go.mod h1:aMif0KXL4g19YCYwsy4Ocjjz5xgPlseYV+B95Oo4JGE=
+knative.dev/test-infra v0.0.0-20200519015156-82551620b0a9 h1:kKfV3QWsxugwXsqgjFd72MjZeAHJ381dWqAxH2KMknc=
+knative.dev/test-infra v0.0.0-20200519015156-82551620b0a9/go.mod h1:A5b2OAXTOeHT3hHhVQm3dmtbuWvIDP7qzgtqxA3/2pE=
 modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
 modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
 modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
diff --git a/hack/update-deps.sh b/hack/update-deps.sh
index 8baa2558f..71b1bf26f 100755
--- a/hack/update-deps.sh
+++ b/hack/update-deps.sh
@@ -26,7 +26,7 @@ cd ${ROOT_DIR}
 # The list of dependencies that we track at HEAD and periodically
 # float forward in this repository.
 FLOATING_DEPS=(
-  "knative.dev/test-infra"
+  "knative.dev/test-infra@release-0.15"
 )
 
 # Parse flags to determine any we should pass to dep.
diff --git a/leaderelection/config.go b/leaderelection/config.go
index 0ec0377ed..59ab45905 100644
--- a/leaderelection/config.go
+++ b/leaderelection/config.go
@@ -19,12 +19,13 @@ package leaderelection
 import (
 	"fmt"
 	"os"
-	"strings"
 	"time"
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/apimachinery/pkg/util/uuid"
+
+	cm "knative.dev/pkg/configmap"
 )
 
 const ConfigMapNameEnv = "CONFIG_LEADERELECTION_NAME"
@@ -35,41 +36,23 @@ var validResourceLocks = sets.NewString("leases", "configmaps", "endpoints")
 func NewConfigFromMap(data map[string]string) (*Config, error) {
 	config := defaultConfig()
 
-	if resourceLock, ok := data["resourceLock"]; ok {
-		if !validResourceLocks.Has(resourceLock) {
-			return nil, fmt.Errorf(`resourceLock: invalid value %q: valid values are "leases","configmaps","endpoints"`, resourceLock)
-		}
-		config.ResourceLock = resourceLock
-	}
+	if err := cm.Parse(data,
+		cm.AsString("resourceLock", &config.ResourceLock),
 
-	for _, d := range []struct {
-		key string
-		val *time.Duration
-	}{{
-		"leaseDuration",
-		&config.LeaseDuration,
-	}, {
-		"renewDeadline",
-		&config.RenewDeadline,
-	}, {
-		"retryPeriod",
-		&config.RetryPeriod,
-	}} {
-		if v, ok := data[d.key]; ok {
-			dur, err := time.ParseDuration(v)
-			if err != nil {
-				return nil, fmt.Errorf("%s: invalid duration: %q", d.key, v)
-			}
-			*d.val = dur
-		}
+		cm.AsDuration("leaseDuration", &config.LeaseDuration),
+		cm.AsDuration("renewDeadline", &config.RenewDeadline),
+		cm.AsDuration("retryPeriod", &config.RetryPeriod),
+
+		// enabledComponents are not validated here, because they are dependent on
+		// the component. Components should provide additional validation for this
+		// field.
+		cm.AsStringSet("enabledComponents", &config.EnabledComponents),
+	); err != nil {
+		return nil, err
 	}
 
-	// enabledComponents are not validated here, because they are dependent on
-	// the component. Components should provide additional validation for this
-	// field.
-	if enabledComponents, ok := data["enabledComponents"]; ok {
-		tokens := strings.Split(enabledComponents, ",")
-		config.EnabledComponents = sets.NewString(tokens...)
+	if !validResourceLocks.Has(config.ResourceLock) {
+		return nil, fmt.Errorf(`resourceLock: invalid value %q: valid values are "leases","configmaps","endpoints"`, config.ResourceLock)
 	}
 
 	return config, nil
diff --git a/metrics/config.go b/metrics/config.go
index 71161baa1..b9c5aa29a 100644
--- a/metrics/config.go
+++ b/metrics/config.go
@@ -33,10 +33,6 @@ import (
 	"knative.dev/pkg/metrics/metricskey"
 )
 
-const (
-	DomainEnv = "METRICS_DOMAIN"
-)
-
 // metricsBackend specifies the backend to use for metrics
 type metricsBackend string
 
@@ -54,6 +50,8 @@ const (
 	StackdriverClusterNameKey = "metrics.stackdriver-cluster-name"
 	StackdriverUseSecretKey   = "metrics.stackdriver-use-secret"
 
+	DomainEnv = "METRICS_DOMAIN"
+
 	// Stackdriver is used for Stackdriver backend
 	Stackdriver metricsBackend = "stackdriver"
 	// Prometheus is used for Prometheus backend
@@ -153,7 +151,12 @@ func NewStackdriverClientConfigFromMap(config map[string]string) *StackdriverCli
 // record applies the `ros` Options to each measurement in `mss` and then records the resulting
 // measurements in the metricsConfig's designated backend.
 func (mc *metricsConfig) record(ctx context.Context, mss []stats.Measurement, ros ...stats.Options) error {
-	if mc == nil || mc.recorder == nil {
+	if mc == nil {
+		// Don't record data points if the metric config is not initialized yet.
+		// At this point, it's unclear whether should record or not.
+		return nil
+	}
+	if mc.recorder == nil {
 		return stats.RecordWithOptions(ctx, append(ros, stats.WithMeasurements(mss...))...)
 	}
 	return mc.recorder(ctx, mss, ros...)
diff --git a/metrics/config_observability.go b/metrics/config_observability.go
index e40cb2fb2..e4560ede0 100644
--- a/metrics/config_observability.go
+++ b/metrics/config_observability.go
@@ -18,10 +18,10 @@ package metrics
 
 import (
 	"os"
-	"strings"
 	texttemplate "text/template"
 
 	corev1 "k8s.io/api/core/v1"
+	cm "knative.dev/pkg/configmap"
 )
 
 const (
@@ -71,32 +71,23 @@ func defaultConfig() *ObservabilityConfig {
 // NewObservabilityConfigFromConfigMap creates a ObservabilityConfig from the supplied ConfigMap
 func NewObservabilityConfigFromConfigMap(configMap *corev1.ConfigMap) (*ObservabilityConfig, error) {
 	oc := defaultConfig()
-	if evlc, ok := configMap.Data["logging.enable-var-log-collection"]; ok {
-		oc.EnableVarLogCollection = strings.EqualFold(evlc, "true")
-	}
 
-	if rut, ok := configMap.Data["logging.revision-url-template"]; ok {
-		oc.LoggingURLTemplate = rut
+	if err := cm.Parse(configMap.Data,
+		cm.AsBool("logging.enable-var-log-collection", &oc.EnableVarLogCollection),
+		cm.AsString("logging.revision-url-template", &oc.LoggingURLTemplate),
+		cm.AsString("logging.request-log-template", &oc.RequestLogTemplate),
+		cm.AsBool("logging.enable-probe-request-log", &oc.EnableProbeRequestLog),
+		cm.AsString("metrics.request-metrics-backend-destination", &oc.RequestMetricsBackend),
+		cm.AsBool("profiling.enable", &oc.EnableProfiling),
+	); err != nil {
+		return nil, err
 	}
 
-	if rlt, ok := configMap.Data["logging.request-log-template"]; ok {
+	if oc.RequestLogTemplate != "" {
 		// Verify that we get valid templates.
-		if _, err := texttemplate.New("requestLog").Parse(rlt); err != nil {
+		if _, err := texttemplate.New("requestLog").Parse(oc.RequestLogTemplate); err != nil {
 			return nil, err
 		}
-		oc.RequestLogTemplate = rlt
-	}
-
-	if eprl, ok := configMap.Data["logging.enable-probe-request-log"]; ok {
-		oc.EnableProbeRequestLog = strings.EqualFold(eprl, "true")
-	}
-
-	if mb, ok := configMap.Data["metrics.request-metrics-backend-destination"]; ok {
-		oc.RequestMetricsBackend = mb
-	}
-
-	if prof, ok := configMap.Data["profiling.enable"]; ok {
-		oc.EnableProfiling = strings.EqualFold(prof, "true")
 	}
 
 	return oc, nil
diff --git a/metrics/metricskey/constants.go b/metrics/metricskey/constants.go
index d9e2a1ace..569067681 100644
--- a/metrics/metricskey/constants.go
+++ b/metrics/metricskey/constants.go
@@ -41,6 +41,12 @@ const (
 	// LabelResponseCodeClass is the label for the HTTP response status code class. For example, "2xx", "3xx", etc.
 	LabelResponseCodeClass = "response_code_class"
 
+	// LabelResponseError is the label for client error. For HTTP, A non-2xx status code doesn't cause an error.
+	LabelResponseError = "response_error"
+
+	// LabelResponseTimeout is the label timeout.
+	LabelResponseTimeout = "response_timeout"
+
 	// ValueUnknown is the default value if the field is unknown, e.g. project will be unknown if Knative
 	// is not running on GKE.
 	ValueUnknown = "unknown"
diff --git a/metrics/utils.go b/metrics/utils.go
index 7345cec92..e2e56a415 100644
--- a/metrics/utils.go
+++ b/metrics/utils.go
@@ -16,7 +16,11 @@ limitations under the License.
 
 package metrics
 
-import "strconv"
+import (
+	"strconv"
+
+	"go.opencensus.io/tag"
+)
 
 // ResponseCodeClass converts an HTTP response code to a string representing its response code class.
 // E.g., The response code class is "5xx" for response code 503.
@@ -24,3 +28,27 @@ func ResponseCodeClass(responseCode int) string {
 	// Get the hundred digit of the response code and concatenate "xx".
 	return strconv.Itoa(responseCode/100) + "xx"
 }
+
+// MaybeInsertIntTag conditionally insert the tag when cond is true.
+func MaybeInsertIntTag(key tag.Key, value int, cond bool) tag.Mutator {
+	if cond {
+		return tag.Insert(key, strconv.Itoa(value))
+	}
+	return tag.Insert(key, "")
+}
+
+// MaybeInsertBoolTag conditionally insert the tag when cond is true.
+func MaybeInsertBoolTag(key tag.Key, value bool, cond bool) tag.Mutator {
+	if cond {
+		return tag.Insert(key, strconv.FormatBool(value))
+	}
+	return tag.Insert(key, "")
+}
+
+// MaybeInsertStringTag conditionally insert the tag when cond is true.
+func MaybeInsertStringTag(key tag.Key, value string, cond bool) tag.Mutator {
+	if cond {
+		return tag.Insert(key, value)
+	}
+	return tag.Insert(key, "")
+}
diff --git a/network/network.go b/network/network.go
index 32015ada3..93037189c 100644
--- a/network/network.go
+++ b/network/network.go
@@ -17,6 +17,8 @@ limitations under the License.
 package network
 
 import (
+	"net/http"
+	"strings"
 	"time"
 )
 
@@ -34,6 +36,12 @@ const (
 	// We should consider exposing this as a configuration.
 	DefaultConnTimeout = 200 * time.Millisecond
 
+	// DefaultDrainTimeout is the time that Knative components on the data
+	// path will wait before shutting down server, but after starting to fail
+	// readiness probes to ensure network layer propagation and so that no requests
+	// are routed to this pod.
+	DefaultDrainTimeout = 30 * time.Second
+
 	// UserAgentKey is the constant for header "User-Agent".
 	UserAgentKey = "User-Agent"
 
@@ -42,4 +50,18 @@ const (
 	// with this header will not be passed to the user container or
 	// included in request metrics.
 	ProbeHeaderName = "K-Network-Probe"
+
+	// Since K8s 1.8, prober requests have
+	//   User-Agent = "kube-probe/{major-version}.{minor-version}".
+	KubeProbeUAPrefix = "kube-probe/"
+
+	// Istio with mTLS rewrites probes, but their probes pass a different
+	// user-agent.  So we augment the probes with this header.
+	KubeletProbeHeaderName = "K-Kubelet-Probe"
 )
+
+// IsKubeletProbe returns true if the request is a Kubernetes probe.
+func IsKubeletProbe(r *http.Request) bool {
+	return strings.HasPrefix(r.Header.Get("User-Agent"), KubeProbeUAPrefix) ||
+		r.Header.Get(KubeletProbeHeaderName) != ""
+}
diff --git a/network/transports.go b/network/transports.go
index 019f4ba87..745e6a5a8 100644
--- a/network/transports.go
+++ b/network/transports.go
@@ -18,7 +18,6 @@ package network
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"net"
 	"net/http"
@@ -54,8 +53,6 @@ var backOffTemplate = wait.Backoff{
 	Steps:    15,
 }
 
-var errDialTimeout = errors.New("timed out dialing")
-
 // DialWithBackOff executes `net.Dialer.DialContext()` with exponentially increasing
 // dial timeouts. In addition it sleeps with random jitter between tries.
 var DialWithBackOff = NewBackoffDialer(backOffTemplate)
@@ -91,7 +88,7 @@ func dialBackOffHelper(ctx context.Context, network, address string, bo wait.Bac
 		}
 		return c, nil
 	}
-	elapsed := time.Now().Sub(start)
+	elapsed := time.Since(start)
 	return nil, fmt.Errorf("timed out dialing after %.2fs", elapsed.Seconds())
 }
 
diff --git a/pool/OWNERS b/pool/OWNERS
new file mode 100644
index 000000000..f4a5f11dc
--- /dev/null
+++ b/pool/OWNERS
@@ -0,0 +1,7 @@
+# The OWNERS file is used by prow to automatically merge approved PRs.
+
+approvers:
+- controller-approvers
+
+labels:
+- area/API
diff --git a/pool/doc.go b/pool/doc.go
new file mode 100644
index 000000000..052b9ee93
--- /dev/null
+++ b/pool/doc.go
@@ -0,0 +1,21 @@
+/*
+Copyright 2020 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
+
+    https://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 pool contains a simple threadpool implementation that accepts
+// work in the form of `func() error` function.  The intent is for it to
+// support similar workloads to errgroup, but with a maximum number of
+// concurrent worker threads.
+package pool
diff --git a/pool/interface.go b/pool/interface.go
new file mode 100644
index 000000000..86cc8e48c
--- /dev/null
+++ b/pool/interface.go
@@ -0,0 +1,36 @@
+/*
+Copyright 2020 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
+
+    https://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 pool
+
+import (
+	"golang.org/x/sync/errgroup"
+)
+
+// Interface defines an errgroup-compatible interface for interacting with
+// our threadpool.
+type Interface interface {
+	// Go queues a single unit of work for execution on this pool. All calls
+	// to Go must be finished before Wait is called.
+	Go(func() error)
+
+	// Wait blocks until all work is complete, returning the first
+	// error returned by any of the work.
+	Wait() error
+}
+
+// errgroup.Group implements Interface
+var _ Interface = (*errgroup.Group)(nil)
diff --git a/pool/pool.go b/pool/pool.go
new file mode 100644
index 000000000..9f298d41b
--- /dev/null
+++ b/pool/pool.go
@@ -0,0 +1,117 @@
+/*
+Copyright 2020 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
+
+    https://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 pool
+
+import (
+	"context"
+	"sync"
+)
+
+type impl struct {
+	wg     sync.WaitGroup
+	workCh chan func() error
+
+	// Ensure that we Wait exactly once and memoize
+	// the result.
+	waitOnce sync.Once
+
+	cancel context.CancelFunc
+
+	// We're only interested in the first result so
+	// only set it once.
+	resultOnce sync.Once
+	result     error
+}
+
+// impl implements Interface
+var _ Interface = (*impl)(nil)
+
+// defaultCapacity is the number of work items or errors that we
+// can queue up before calls to Go will block, or work will
+// block until Wait is called.
+const defaultCapacity = 50
+
+// New creates a fresh worker pool with the specified size.
+func New(workers int) Interface {
+	return NewWithCapacity(workers, defaultCapacity)
+}
+
+// NewWithCapacity creates a fresh worker pool with the specified size.
+func NewWithCapacity(workers, capacity int) Interface {
+	i, _ := NewWithContext(context.Background(), workers, capacity)
+	return i
+}
+
+// NewWithContext creates a pool that is driven by a cancelable context.
+// Just like errgroup.Group on first error the context will be canceled as well.
+func NewWithContext(ctx context.Context, workers, capacity int) (Interface, context.Context) {
+	ctx, cancel := context.WithCancel(ctx)
+	i := &impl{
+		cancel: cancel,
+		workCh: make(chan func() error, capacity),
+	}
+
+	// Start a go routine for each worker, which:
+	// 1. reads off of the work channel,
+	// 2. (optionally) sets the error as the result,
+	// 3. marks work as done in our sync.WaitGroup.
+	for idx := 0; idx < workers; idx++ {
+		go func() {
+			for work := range i.workCh {
+				i.exec(work)
+			}
+		}()
+	}
+	return i, ctx
+}
+
+func (i *impl) exec(w func() error) {
+	defer i.wg.Done()
+	if err := w(); err != nil {
+		i.resultOnce.Do(func() {
+			if i.cancel != nil {
+				i.cancel()
+			}
+			i.result = err
+		})
+	}
+}
+
+// Go implements Interface.
+func (i *impl) Go(w func() error) {
+	// Increment the amount of outstanding work we're waiting on.
+	i.wg.Add(1)
+	// Send the work along the queue.
+	i.workCh <- w
+}
+
+// Wait implements Interface.
+func (i *impl) Wait() error {
+	i.waitOnce.Do(func() {
+		// Wait for queued work to complete.
+		i.wg.Wait()
+		// Notify the context, that it's done now.
+		if i.cancel != nil {
+			i.cancel()
+		}
+
+		// Now we know there are definitely no new items arriving.
+		close(i.workCh)
+	})
+
+	return i.result
+}
diff --git a/reconciler/reconcile_common.go b/reconciler/reconcile_common.go
index 38e3bbe38..cc0e2d712 100644
--- a/reconciler/reconcile_common.go
+++ b/reconciler/reconcile_common.go
@@ -29,7 +29,7 @@ const failedGenerationBump = "NewObservedGenFailure"
 func PreProcessReconcile(ctx context.Context, resource duckv1.KRShaped) {
 	newStatus := resource.GetStatus()
 
-	if newStatus.ObservedGeneration != resource.GetObjectMeta().GetGeneration() {
+	if newStatus.ObservedGeneration != resource.GetGeneration() {
 		condSet := resource.GetConditionSet()
 		manager := condSet.Manage(newStatus)
 
@@ -46,10 +46,11 @@ func PostProcessReconcile(ctx context.Context, resource duckv1.KRShaped) {
 
 	// Bump observed generation to denote that we have processed this
 	// generation regardless of success or failure.
-	newStatus.ObservedGeneration = resource.GetObjectMeta().GetGeneration()
+	newStatus.ObservedGeneration = resource.GetGeneration()
 
-	rc := mgr.GetTopLevelCondition()
-	if rc.Reason == failedGenerationBump {
+	if rc := mgr.GetTopLevelCondition(); rc == nil {
+		logger.Warn("A reconciliation included no top-level condition")
+	} else if rc.Reason == failedGenerationBump {
 		logger.Warn("A reconciler observed a new generation without updating the resource status")
 	}
 }
diff --git a/source/source_stats_reporter.go b/source/source_stats_reporter.go
index 482c5a42e..b22eff94a 100644
--- a/source/source_stats_reporter.go
+++ b/source/source_stats_reporter.go
@@ -18,7 +18,6 @@ package source
 
 import (
 	"context"
-	"strconv"
 
 	"go.opencensus.io/stats/view"
 	"knative.dev/pkg/metrics"
@@ -48,6 +47,8 @@ var (
 	sourceResourceGroupKey = tag.MustNewKey(metricskey.LabelResourceGroup)
 	responseCodeKey        = tag.MustNewKey(metricskey.LabelResponseCode)
 	responseCodeClassKey   = tag.MustNewKey(metricskey.LabelResponseCodeClass)
+	responseError          = tag.MustNewKey(metricskey.LabelResponseError)
+	responseTimeout        = tag.MustNewKey(metricskey.LabelResponseTimeout)
 )
 
 type ReportArgs struct {
@@ -56,6 +57,8 @@ type ReportArgs struct {
 	EventSource   string
 	Name          string
 	ResourceGroup string
+	Error         string
+	Timeout       bool
 }
 
 func init() {
@@ -103,8 +106,10 @@ func (r *reporter) generateTag(args *ReportArgs, responseCode int) (context.Cont
 		tag.Insert(eventTypeKey, args.EventType),
 		tag.Insert(sourceNameKey, args.Name),
 		tag.Insert(sourceResourceGroupKey, args.ResourceGroup),
-		tag.Insert(responseCodeKey, strconv.Itoa(responseCode)),
-		tag.Insert(responseCodeClassKey, metrics.ResponseCodeClass(responseCode)))
+		metrics.MaybeInsertIntTag(responseCodeKey, responseCode, responseCode > 0),
+		metrics.MaybeInsertStringTag(responseCodeClassKey, metrics.ResponseCodeClass(responseCode), responseCode > 0),
+		tag.Insert(responseError, args.Error),
+		metrics.MaybeInsertBoolTag(responseTimeout, args.Timeout, args.Error != ""))
 }
 
 func register() {
@@ -115,7 +120,9 @@ func register() {
 		sourceNameKey,
 		sourceResourceGroupKey,
 		responseCodeKey,
-		responseCodeClassKey}
+		responseCodeClassKey,
+		responseError,
+		responseTimeout}
 
 	// Create view to see our measurements.
 	if err := view.Register(
diff --git a/tracing/config/tracing.go b/tracing/config/tracing.go
index 67aec651c..b3aceee16 100644
--- a/tracing/config/tracing.go
+++ b/tracing/config/tracing.go
@@ -25,6 +25,7 @@ import (
 
 	"cloud.google.com/go/compute/metadata"
 	corev1 "k8s.io/api/core/v1"
+	cm "knative.dev/pkg/configmap"
 )
 
 const (
@@ -98,15 +99,20 @@ func NewTracingConfigFromMap(cfgMap map[string]string) (*Config, error) {
 		}
 	}
 
-	if endpoint, ok := cfgMap[zipkinEndpointKey]; !ok && tc.Backend == Zipkin {
+	if err := cm.Parse(cfgMap,
+		cm.AsString(zipkinEndpointKey, &tc.ZipkinEndpoint),
+		cm.AsString(stackdriverProjectIDKey, &tc.StackdriverProjectID),
+		cm.AsBool(debugKey, &tc.Debug),
+		cm.AsFloat64(sampleRateKey, &tc.SampleRate),
+	); err != nil {
+		return nil, err
+	}
+
+	if tc.Backend == Zipkin && tc.ZipkinEndpoint == "" {
 		return nil, errors.New("zipkin tracing enabled without a zipkin endpoint specified")
-	} else {
-		tc.ZipkinEndpoint = endpoint
 	}
 
-	if projectID, ok := cfgMap[stackdriverProjectIDKey]; ok {
-		tc.StackdriverProjectID = projectID
-	} else if tc.Backend == Stackdriver {
+	if tc.Backend == Stackdriver && tc.StackdriverProjectID == "" {
 		projectID, err := metadata.ProjectID()
 		if err != nil {
 			return nil, fmt.Errorf("stackdriver tracing enabled without a project-id specified: %w", err)
@@ -114,23 +120,8 @@ func NewTracingConfigFromMap(cfgMap map[string]string) (*Config, error) {
 		tc.StackdriverProjectID = projectID
 	}
 
-	if debug, ok := cfgMap[debugKey]; ok {
-		debugBool, err := strconv.ParseBool(debug)
-		if err != nil {
-			return nil, fmt.Errorf("failed parsing tracing config %q", debugKey)
-		}
-		tc.Debug = debugBool
-	}
-
-	if sampleRate, ok := cfgMap[sampleRateKey]; ok {
-		sampleRateFloat, err := strconv.ParseFloat(sampleRate, 64)
-		if err != nil {
-			return nil, fmt.Errorf("failed to parse sampleRate in tracing config: %w", err)
-		}
-		if sampleRateFloat < 0 || sampleRateFloat > 1 {
-			return nil, fmt.Errorf("sampleRate = %v must be in [0, 1] range", sampleRateFloat)
-		}
-		tc.SampleRate = sampleRateFloat
+	if tc.SampleRate < 0 || tc.SampleRate > 1 {
+		return nil, fmt.Errorf("sample-rate = %v must be in [0, 1] range", tc.SampleRate)
 	}
 
 	return tc, nil
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 7e75c0b1b..b1642c3ab 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -914,7 +914,7 @@ k8s.io/utils/buffer
 k8s.io/utils/integer
 k8s.io/utils/pointer
 k8s.io/utils/trace
-# knative.dev/test-infra v0.0.0-20200508015845-8d7d46a46176
+# knative.dev/test-infra v0.0.0-20200519015156-82551620b0a9
 ## explicit
 knative.dev/test-infra/scripts
 # sigs.k8s.io/yaml v1.1.0
diff --git a/webhook/env.go b/webhook/env.go
new file mode 100644
index 000000000..9e59f7cfd
--- /dev/null
+++ b/webhook/env.go
@@ -0,0 +1,39 @@
+/*
+Copyright 2020 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 webhook
+
+import (
+	"fmt"
+	"os"
+	"strconv"
+)
+
+const portEnvKey = "WEBHOOK_PORT"
+
+// PortFromEnv returns the webhook port set by portEnvKey, or default port if env var is not set.
+func PortFromEnv(defaultPort int) int {
+	if os.Getenv(portEnvKey) == "" {
+		return defaultPort
+	}
+	port, err := strconv.Atoi(os.Getenv(portEnvKey))
+	if err != nil {
+		panic(fmt.Sprintf("failed to convert the environment variable %q : %v", portEnvKey, err))
+	} else if port == 0 {
+		panic(fmt.Sprintf("the environment variable %q can't be zero", portEnvKey))
+	}
+	return port
+}
diff --git a/webhook/webhook.go b/webhook/webhook.go
index 3315ba163..e29ea9611 100644
--- a/webhook/webhook.go
+++ b/webhook/webhook.go
@@ -22,6 +22,7 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"time"
 
 	// Injection stuff
 	kubeclient "knative.dev/pkg/client/injection/kube/client"
@@ -33,6 +34,7 @@ import (
 	"k8s.io/client-go/kubernetes"
 	corelisters "k8s.io/client-go/listers/core/v1"
 	"knative.dev/pkg/logging"
+	"knative.dev/pkg/network"
 	"knative.dev/pkg/system"
 	certresources "knative.dev/pkg/webhook/certificates/resources"
 )
@@ -71,6 +73,13 @@ const (
 	Connect Operation = admissionv1beta1.Connect
 )
 
+var (
+	// GracePeriod is the duration that the webhook will wait after it's
+	// context is cancelled (and probes are failing) before shutting down
+	// the http server.
+	GracePeriod = 30 * time.Second
+)
+
 // Webhook implements the external webhook for validation of
 // resources and configuration.
 type Webhook struct {
@@ -81,6 +90,12 @@ type Webhook struct {
 	// synced is function that is called when the informers have been synced.
 	synced context.CancelFunc
 
+	// stopCh is closed when we should start failing readiness probes.
+	stopCh chan struct{}
+	// grace period is how long to wait after failing readiness probes
+	// before shutting down.
+	gracePeriod time.Duration
+
 	mux          http.ServeMux
 	secretlister corelisters.SecretLister
 }
@@ -129,6 +144,8 @@ func New(
 		secretlister: secretInformer.Lister(),
 		Logger:       logger,
 		synced:       cancel,
+		stopCh:       make(chan struct{}),
+		gracePeriod:  GracePeriod,
 	}
 
 	webhook.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
@@ -193,8 +210,6 @@ func (wh *Webhook) Run(stop <-chan struct{}) error {
 		},
 	}
 
-	logger.Info("Found certificates for webhook...")
-
 	eg, ctx := errgroup.WithContext(ctx)
 	eg.Go(func() error {
 		if err := server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
@@ -206,16 +221,40 @@ func (wh *Webhook) Run(stop <-chan struct{}) error {
 
 	select {
 	case <-stop:
-		if err := server.Close(); err != nil {
-			return err
-		}
+		eg.Go(func() error {
+			// Start failing readiness probes immediately.
+			logger.Info("Starting to fail readiness probes...")
+			close(wh.stopCh)
+
+			// Wait for a grace period for the above to take effect and this Pod's
+			// endpoint to be removed from the webhook service's Endpoints.
+			// For this to be effective, it must be greater than the probe's
+			// periodSeconds times failureThreshold by a margin suitable to
+			// propagate the new Endpoints data across the cluster.
+			time.Sleep(wh.gracePeriod)
+
+			return server.Shutdown(context.Background())
+		})
+
+		// Wait for all outstanding go routined to terminate, including our new one.
 		return eg.Wait()
+
 	case <-ctx.Done():
 		return fmt.Errorf("webhook server bootstrap failed %w", ctx.Err())
 	}
 }
 
 func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	// Respond to probes regardless of path.
+	if network.IsKubeletProbe(r) {
+		select {
+		case <-wh.stopCh:
+			http.Error(w, "shutting down", http.StatusInternalServerError)
+		default:
+			w.WriteHeader(http.StatusOK)
+		}
+	}
+
 	// Verify the content type is accurate.
 	contentType := r.Header.Get("Content-Type")
 	if contentType != "application/json" {


Test patch:
diff --git a/apiextensions/storageversion/migrator_test.go b/apiextensions/storageversion/migrator_test.go
index c2705c8a4..ff73b3b16 100644
--- a/apiextensions/storageversion/migrator_test.go
+++ b/apiextensions/storageversion/migrator_test.go
@@ -18,7 +18,7 @@ package storageversion
 
 import (
 	"context"
-	"fmt"
+	"errors"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -71,8 +71,8 @@ func TestMigrate(t *testing.T) {
 	cclient := apixFake.NewSimpleClientset(fakeCRD)
 	m := NewMigrator(dclient, cclient)
 
-	if err := m.Migrate(context.TODO(), fakeGR); err != nil {
-		t.Fatalf("Migrate() = %s", err)
+	if err := m.Migrate(context.Background(), fakeGR); err != nil {
+		t.Fatalf("Migrate() = %v", err)
 	}
 
 	assertPatches(t, dclient.Actions(),
@@ -102,7 +102,7 @@ func TestMigrate_Errors(t *testing.T) {
 		crd: func(fake *k8stesting.Fake) {
 			fake.PrependReactor("get", "*",
 				func(k8stesting.Action) (bool, runtime.Object, error) {
-					return true, nil, fmt.Errorf("failed to get crd")
+					return true, nil, errors.New("failed to get crd")
 				})
 		},
 	}, {
@@ -110,7 +110,7 @@ func TestMigrate_Errors(t *testing.T) {
 		dyn: func(fake *k8stesting.Fake) {
 			fake.PrependReactor("list", "*",
 				func(k8stesting.Action) (bool, runtime.Object, error) {
-					return true, nil, fmt.Errorf("failed to list resources")
+					return true, nil, errors.New("failed to list resources")
 				})
 		},
 	}, {
@@ -118,7 +118,7 @@ func TestMigrate_Errors(t *testing.T) {
 		dyn: func(fake *k8stesting.Fake) {
 			fake.PrependReactor("patch", "*",
 				func(k8stesting.Action) (bool, runtime.Object, error) {
-					return true, nil, fmt.Errorf("failed to patch resources")
+					return true, nil, errors.New("failed to patch resources")
 				})
 		},
 	}, {
@@ -126,7 +126,7 @@ func TestMigrate_Errors(t *testing.T) {
 		crd: func(fake *k8stesting.Fake) {
 			fake.PrependReactor("patch", "*",
 				func(k8stesting.Action) (bool, runtime.Object, error) {
-					return true, nil, fmt.Errorf("failed to patch definition")
+					return true, nil, errors.New("failed to patch definition")
 				})
 		},
 	},
@@ -148,7 +148,7 @@ func TestMigrate_Errors(t *testing.T) {
 			}
 
 			m := NewMigrator(dclient, cclient)
-			if err := m.Migrate(context.TODO(), fakeGR); err == nil {
+			if err := m.Migrate(context.Background(), fakeGR); err == nil {
 				t.Error("Migrate should have returned an error")
 			}
 		})
@@ -160,7 +160,7 @@ func assertPatches(t *testing.T, actions []k8stesting.Action, want ...k8stesting
 
 	got := getPatchActions(actions)
 	if diff := cmp.Diff(got, want); diff != "" {
-		t.Errorf("unexpected patches: %s", diff)
+		t.Error("Unexpected patches:", diff)
 	}
 }
 
diff --git a/apis/deprecated_test.go b/apis/deprecated_test.go
index 3b5bde44f..cbcac983f 100644
--- a/apis/deprecated_test.go
+++ b/apis/deprecated_test.go
@@ -490,7 +490,7 @@ func TestCheckDeprecatedUpdate(t *testing.T) {
 	}
 	for n, tc := range testCases {
 		t.Run(n, func(t *testing.T) {
-			ctx := context.TODO()
+			ctx := context.Background()
 			if tc.strict {
 				ctx = apis.DisallowDeprecated(ctx)
 			}
diff --git a/apis/duck/cached_test.go b/apis/duck/cached_test.go
index 9f945f138..239a477c0 100644
--- a/apis/duck/cached_test.go
+++ b/apis/duck/cached_test.go
@@ -57,7 +57,7 @@ func TestSameGVR(t *testing.T) {
 	// counts the number of calls to cif.Get that returned
 	retGetCount := int32(0)
 
-	errGrp, _ := errgroup.WithContext(context.TODO())
+	errGrp, _ := errgroup.WithContext(context.Background())
 
 	// Use the same GVR each iteration to ensure we hit the cache and don't
 	// initialize the informerCache for that GVR multiple times through our
@@ -116,7 +116,7 @@ func TestDifferentGVRs(t *testing.T) {
 	// counts the number of calls to cif.Get that returned
 	retGetCount := int32(0)
 
-	errGrp, _ := errgroup.WithContext(context.TODO())
+	errGrp, _ := errgroup.WithContext(context.Background())
 
 	const iter = 10
 	for i := 0; i < iter; i++ {
diff --git a/apis/duck/v1beta1/destination_test.go b/apis/duck/v1beta1/destination_test.go
index 0f06802f2..d0284ae35 100644
--- a/apis/duck/v1beta1/destination_test.go
+++ b/apis/duck/v1beta1/destination_test.go
@@ -32,7 +32,7 @@ const (
 )
 
 func TestValidateDestination(t *testing.T) {
-	ctx := context.TODO()
+	ctx := context.Background()
 
 	validRef := corev1.ObjectReference{
 		Kind:       kind,
@@ -205,7 +205,7 @@ func TestValidateDestination(t *testing.T) {
 }
 
 func TestValidateDestinationDisallowDeprecated(t *testing.T) {
-	ctx := context.TODO()
+	ctx := context.Background()
 
 	validRef := corev1.ObjectReference{
 		Kind:       kind,
diff --git a/apis/test/example/v1alpha1/fiz_types.go b/apis/test/example/v1alpha1/fiz_types.go
index b87559e2e..8b0fbe472 100644
--- a/apis/test/example/v1alpha1/fiz_types.go
+++ b/apis/test/example/v1alpha1/fiz_types.go
@@ -94,11 +94,6 @@ func (f *ClusterFiz) Validate(ctx context.Context) *apis.FieldError {
 	return nil
 }
 
-// GetTypeMeta retrieves the ObjectMeta of the ClusterFiz. Implements the KRShaped interface.
-func (f *ClusterFiz) GetTypeMeta() *metav1.TypeMeta {
-	return &f.TypeMeta
-}
-
 // GetStatus retrieves the status of the ClusterFiz. Implements the KRShaped interface.
 func (f *ClusterFiz) GetStatus() *duckv1.Status {
 	return &f.Status.Status
diff --git a/apis/test/example/v1alpha1/foo_types.go b/apis/test/example/v1alpha1/foo_types.go
index ebe255341..c681ea796 100644
--- a/apis/test/example/v1alpha1/foo_types.go
+++ b/apis/test/example/v1alpha1/foo_types.go
@@ -93,11 +93,6 @@ func (f *Foo) Validate(ctx context.Context) *apis.FieldError {
 	return nil
 }
 
-// GetTypeMeta retrieves the ObjectMeta of the Foo. Implements the KRShaped interface.
-func (f *Foo) GetTypeMeta() *metav1.TypeMeta {
-	return &f.TypeMeta
-}
-
 // GetStatus retrieves the status of the Foo Implements the KRShaped interface.
 func (f *Foo) GetStatus() *duckv1.Status {
 	return &f.Status.Status
diff --git a/apis/test/pub/v1alpha1/bar_types.go b/apis/test/pub/v1alpha1/bar_types.go
index 826dae3fb..48e4f081b 100644
--- a/apis/test/pub/v1alpha1/bar_types.go
+++ b/apis/test/pub/v1alpha1/bar_types.go
@@ -93,11 +93,6 @@ func (b *Bar) Validate(ctx context.Context) *apis.FieldError {
 	return nil
 }
 
-// GetTypeMeta retrieves the ObjectMeta of the Bar. Implements the KRShaped interface.
-func (b *Bar) GetTypeMeta() *metav1.TypeMeta {
-	return &b.TypeMeta
-}
-
 // GetStatus retrieves the status of the Bar. Implements the KRShaped interface.
 func (b *Bar) GetStatus() *duckv1.Status {
 	return &b.Status.Status
diff --git a/codegen/cmd/injection-gen/generators/comment_parser_test.go b/codegen/cmd/injection-gen/generators/comment_parser_test.go
index db2a7b757..e45dd3004 100644
--- a/codegen/cmd/injection-gen/generators/comment_parser_test.go
+++ b/codegen/cmd/injection-gen/generators/comment_parser_test.go
@@ -39,15 +39,15 @@ func TestParseComments(t *testing.T) {
 	}
 
 	if val, ok := extracted["with"]; !ok || val["option"] != "" {
-		t.Errorf("Failed to extract single key got=%t,%v want=true,{\"option\":\"\"}", ok, val)
+		t.Errorf(`Failed to extract single key got=%t,%v want=true,{"option":""}`, ok, val)
 	}
 
 	if val, ok := extracted["pair"]; !ok || val["key"] != "value" {
-		t.Errorf("Failed to extract single key got=%t,%v want=true,{\"key\":\"value\"}", ok, val)
+		t.Errorf(`Failed to extract single key got=%t,%v want=true,{"key":"value"}`, ok, val)
 	}
 
 	if val, ok := extracted["manypairs"]; !ok || val["key1"] != "value1" || val["key2"] != "value2" {
-		t.Errorf("Failed to extract single key got=%t,%v want=true,{\"key\":\"value\"}", ok, val)
+		t.Errorf(`Failed to extract single key got=%t,%v want=true,{"key":"value"}`, ok, val)
 	}
 }
 
@@ -71,14 +71,14 @@ func TestMergeDuplicates(t *testing.T) {
 	}
 
 	if val, ok := extracted["bar"]; !ok || val["key"] != "value" {
-		t.Errorf("Failed to extract single key got=%t,%v want=true,{\"key\":\"value\"}", ok, val)
+		t.Errorf(`Failed to extract single key got=%t,%v want=true,{"key":"value"}`, ok, val)
 	}
 
 	if val, ok := extracted["manypairs"]; !ok || val["key1"] != "value1" || val["key2"] != "value2" {
-		t.Errorf("Failed to extract single key got=%t,%v want=true,{\"key\":\"value\"}", ok, val)
+		t.Errorf(`Failed to extract single key got=%t,%v want=true,{"key":"value"}`, ok, val)
 	}
 
 	if val, ok := extracted["oops"]; !ok || val != nil {
-		t.Errorf("Failed to extract single key got=%t,%v want=true,{\"oops\":nil}", ok, val)
+		t.Errorf(`Failed to extract single key got=%t,%v want=true,{"oops":nil}`, ok, val)
 	}
 }
diff --git a/configmap/parse_test.go b/configmap/parse_test.go
new file mode 100644
index 000000000..14528d351
--- /dev/null
+++ b/configmap/parse_test.go
@@ -0,0 +1,135 @@
+/*
+Copyright 2020 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 configmap
+
+import (
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"k8s.io/apimachinery/pkg/util/sets"
+)
+
+type testConfig struct {
+	str string
+	boo bool
+	i32 int32
+	i64 int64
+	f64 float64
+	dur time.Duration
+	set sets.String
+}
+
+func TestParse(t *testing.T) {
+	tests := []struct {
+		name      string
+		conf      testConfig
+		data      map[string]string
+		want      testConfig
+		expectErr bool
+	}{{
+		name: "all good",
+		data: map[string]string{
+			"test-string":   "foo.bar",
+			"test-bool":     "true",
+			"test-int32":    "1",
+			"test-int64":    "2",
+			"test-float64":  "1.0",
+			"test-duration": "1m",
+			"test-set":      "a,b,c",
+		},
+		want: testConfig{
+			str: "foo.bar",
+			boo: true,
+			i32: 1,
+			i64: 2,
+			f64: 1.0,
+			dur: time.Minute,
+			set: sets.NewString("a", "b", "c"),
+		},
+	}, {
+		name: "respect defaults",
+		conf: testConfig{
+			str: "foo.bar",
+			boo: true,
+			i32: 1,
+			i64: 2,
+			f64: 1.0,
+			dur: time.Minute,
+		},
+		want: testConfig{
+			str: "foo.bar",
+			boo: true,
+			i32: 1,
+			i64: 2,
+			f64: 1.0,
+			dur: time.Minute,
+		},
+	}, {
+		name: "bool defaults to false",
+		data: map[string]string{
+			"test-bool": "foo",
+		},
+		want: testConfig{
+			boo: false,
+		},
+	}, {
+		name: "int32 error",
+		data: map[string]string{
+			"test-int32": "foo",
+		},
+		expectErr: true,
+	}, {
+		name: "int64 error",
+		data: map[string]string{
+			"test-int64": "foo",
+		},
+		expectErr: true,
+	}, {
+		name: "float64 error",
+		data: map[string]string{
+			"test-float64": "foo",
+		},
+		expectErr: true,
+	}, {
+		name: "duration error",
+		data: map[string]string{
+			"test-duration": "foo",
+		},
+		expectErr: true,
+	}}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if err := Parse(test.data,
+				AsString("test-string", &test.conf.str),
+				AsBool("test-bool", &test.conf.boo),
+				AsInt32("test-int32", &test.conf.i32),
+				AsInt64("test-int64", &test.conf.i64),
+				AsFloat64("test-float64", &test.conf.f64),
+				AsDuration("test-duration", &test.conf.dur),
+				AsStringSet("test-set", &test.conf.set),
+			); (err == nil) == test.expectErr {
+				t.Fatal("Failed to parse data:", err)
+			}
+
+			if !cmp.Equal(test.conf, test.want, cmp.AllowUnexported(testConfig{})) {
+				t.Fatalf("parsed = %v, want %v", test.conf, test.want)
+			}
+		})
+	}
+}
diff --git a/kmp/reporters_test.go b/kmp/reporters_test.go
index 29e1b729b..2daaf42d7 100644
--- a/kmp/reporters_test.go
+++ b/kmp/reporters_test.go
@@ -31,7 +31,7 @@ type testStruct struct {
 	Omit        string      `json:"omit,omitempty"`
 	Ignore      string      `json:"-"`
 	Dash        string      `json:"-,"`
-	//lint:ignore SA5008 we actually want to test this broken json options work
+	//nolint:staticcheck // We actually want to test this broken json options work
 	MultiComma string `json:"multi,omitempty,somethingelse"`
 }
 
diff --git a/leaderelection/config_test.go b/leaderelection/config_test.go
index 8621be5c5..3d3767788 100644
--- a/leaderelection/config_test.go
+++ b/leaderelection/config_test.go
@@ -18,7 +18,6 @@ package leaderelection
 
 import (
 	"errors"
-	"reflect"
 	"testing"
 	"time"
 
@@ -84,19 +83,19 @@ func TestNewConfigMapFromData(t *testing.T) {
 		data: kmeta.UnionMaps(okData(), map[string]string{
 			"leaseDuration": "flops",
 		}),
-		err: errors.New(`leaseDuration: invalid duration: "flops"`),
+		err: errors.New(`failed to parse "leaseDuration": time: invalid duration flops`),
 	}, {
 		name: "invalid renewDeadline",
 		data: kmeta.UnionMaps(okData(), map[string]string{
 			"renewDeadline": "flops",
 		}),
-		err: errors.New(`renewDeadline: invalid duration: "flops"`),
+		err: errors.New(`failed to parse "renewDeadline": time: invalid duration flops`),
 	}, {
 		name: "invalid retryPeriod",
 		data: kmeta.UnionMaps(okData(), map[string]string{
 			"retryPeriod": "flops",
 		}),
-		err: errors.New(`retryPeriod: invalid duration: "flops"`),
+		err: errors.New(`failed to parse "retryPeriod": time: invalid duration flops`),
 	}}
 
 	for _, tc := range cases {
@@ -105,7 +104,8 @@ func TestNewConfigMapFromData(t *testing.T) {
 				&corev1.ConfigMap{
 					Data: tc.data,
 				})
-			if !reflect.DeepEqual(tc.err, actualErr) {
+
+			if tc.err != nil && tc.err.Error() != actualErr.Error() {
 				t.Fatalf("Error = %v, want: %v", actualErr, tc.err)
 			}
 
diff --git a/logging/config_test.go b/logging/config_test.go
index ab4c3e2f8..a86c73a38 100644
--- a/logging/config_test.go
+++ b/logging/config_test.go
@@ -268,8 +268,8 @@ func TestEmptyComponentName(t *testing.T) {
 	}
 	// The empty string component should have been ignored, so it should be the default Info, rather
 	// than Error as set in the config map.
-	if l := c.LoggingLevel[""]; l != zapcore.InfoLevel {
-		t.Errorf("Expected default Info level for LoggingLevel[\"\"]. got: %v", l)
+	if got := c.LoggingLevel[""]; got != zapcore.InfoLevel {
+		t.Errorf(`LoggingLevel[""] = %v, want: InfoLevel`, got)
 	}
 }
 
@@ -288,7 +288,7 @@ func TestUpdateLevelFromConfigMap(t *testing.T) {
 			Name:      "config-logging",
 		},
 		Data: map[string]string{
-			loggerConfigKey: fmt.Sprintf("{\"level\": %q}", globalLevel),
+			loggerConfigKey: fmt.Sprintf(`{"level": %q}`, globalLevel),
 			componentLogKey: componentLevel.String(),
 		},
 	}
@@ -452,7 +452,7 @@ type testConfigOption func(*Config) *Config
 func withGlobalLevel(lvl string) testConfigOption {
 	return func(cfg *Config) *Config {
 		cfg.LoggingConfig = fmt.Sprintf("{"+
-			"\"level\": %q, "+
+			`"level": %q, `+
 			`"outputPaths": ["stdout"], `+
 			`"errorOutputPaths": ["stderr"], `+
 			`"encoding": "json"`+
diff --git a/logging/testing/util.go b/logging/testing/util.go
index f2a8d3540..2507198d5 100644
--- a/logging/testing/util.go
+++ b/logging/testing/util.go
@@ -41,5 +41,5 @@ func ClearAll() {}
 
 // TestContextWithLogger returns a context with a logger to be used in tests
 func TestContextWithLogger(t zaptest.TestingT) context.Context {
-	return logging.WithLogger(context.TODO(), TestLogger(t))
+	return logging.WithLogger(context.Background(), TestLogger(t))
 }
diff --git a/metrics/config_test.go b/metrics/config_test.go
index 1fd0d18dd..0b3cb1f99 100644
--- a/metrics/config_test.go
+++ b/metrics/config_test.go
@@ -71,7 +71,7 @@ var (
 			Domain:    servingDomain,
 			Component: testComponent,
 		},
-		expectedErr: "unsupported metrics backend value \"unsupported\"",
+		expectedErr: `unsupported metrics backend value "unsupported"`,
 	}, {
 		name: "emptyDomain",
 		ops: ExporterOptions{
@@ -102,7 +102,7 @@ var (
 			Domain:    servingDomain,
 			Component: testComponent,
 		},
-		expectedErr: "invalid metrics.reporting-period-seconds value \"test\"",
+		expectedErr: `invalid metrics.reporting-period-seconds value "test"`,
 	}, {
 		name: "invalidOpenCensusSecuritySetting",
 		ops: ExporterOptions{
@@ -113,7 +113,7 @@ var (
 			Domain:    servingDomain,
 			Component: testComponent,
 		},
-		expectedErr: "invalid metrics.opencensus-require-tls value \"yep\"",
+		expectedErr: `invalid metrics.opencensus-require-tls value "yep"`,
 	}, {
 		name: "invalidAllowStackdriverCustomMetrics",
 		ops: ExporterOptions{
@@ -124,7 +124,7 @@ var (
 			Domain:    servingDomain,
 			Component: testComponent,
 		},
-		expectedErr: "invalid metrics.allow-stackdriver-custom-metrics value \"test\"",
+		expectedErr: `invalid metrics.allow-stackdriver-custom-metrics value "test"`,
 	}, {
 		name: "tooSmallPrometheusPort",
 		ops: ExporterOptions{
diff --git a/metrics/record_test.go b/metrics/record_test.go
index 80c4d2f05..7a3568bc8 100644
--- a/metrics/record_test.go
+++ b/metrics/record_test.go
@@ -58,9 +58,6 @@ func TestRecordServing(t *testing.T) {
 			stackdriverMetricTypePrefix: "knative.dev/unsupported",
 		},
 		measurement: measure.M(3),
-	}, {
-		name:        "empty metricsConfig",
-		measurement: measure.M(4),
 	}}
 	testRecord(t, measure, shouldReportCases)
 }
@@ -87,9 +84,6 @@ func TestRecordEventing(t *testing.T) {
 			stackdriverMetricTypePrefix: "knative.dev/unsupported",
 		},
 		measurement: measure.M(3),
-	}, {
-		name:        "empty metricsConfig",
-		measurement: measure.M(4),
 	}}
 	testRecord(t, measure, shouldReportCases)
 }
@@ -149,6 +143,9 @@ func testRecord(t *testing.T, measure *stats.Int64Measure, shouldReportCases []c
 			},
 		},
 		measurement: measure.M(5),
+	}, {
+		name:        "empty metricsConfig",
+		measurement: measure.M(4),
 	}}
 
 	for _, test := range shouldNotReportCases {
diff --git a/network/network_test.go b/network/network_test.go
new file mode 100644
index 000000000..dc0eb2eb2
--- /dev/null
+++ b/network/network_test.go
@@ -0,0 +1,44 @@
+/*
+Copyright 2020 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
+
+    https://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 network
+
+import (
+	"net/http"
+	"testing"
+)
+
+func TestIsKubeletProbe(t *testing.T) {
+	req, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
+	if err != nil {
+		t.Fatal("Error building request:", err)
+	}
+	if IsKubeletProbe(req) {
+		t.Error("Not a kubelet probe but counted as such")
+	}
+	req.Header.Set("User-Agent", KubeProbeUAPrefix+"1.14")
+	if !IsKubeletProbe(req) {
+		t.Error("kubelet probe but not counted as such")
+	}
+	req.Header.Del("User-Agent")
+	if IsKubeletProbe(req) {
+		t.Error("Not a kubelet probe but counted as such")
+	}
+	req.Header.Set(KubeletProbeHeaderName, "no matter")
+	if !IsKubeletProbe(req) {
+		t.Error("kubelet probe but not counted as such")
+	}
+}
diff --git a/pool/pool_test.go b/pool/pool_test.go
new file mode 100644
index 000000000..2cbab7a1b
--- /dev/null
+++ b/pool/pool_test.go
@@ -0,0 +1,246 @@
+/*
+Copyright 2020 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
+
+    https://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 pool
+
+import (
+	"context"
+	"errors"
+	"sync"
+	"sync/atomic"
+	"testing"
+	"time"
+)
+
+func TestParallelismNoErrors(t *testing.T) {
+	tests := []struct {
+		name string
+		size int
+		work int
+	}{{
+		name: "single threaded",
+		size: 1,
+		work: 3,
+	}, {
+		name: "three workers",
+		size: 3,
+		work: 10,
+	}, {
+		name: "ten workers",
+		size: 10,
+		work: 100,
+	}}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			var (
+				// m guards max.
+				m      sync.Mutex
+				max    int32
+				active int32
+			)
+
+			// Use our own waitgroup to ensure that the work
+			// can all complete before we block on the error
+			// result.
+			wg := &sync.WaitGroup{}
+
+			worker := func() error {
+				defer wg.Done()
+				na := atomic.AddInt32(&active, 1)
+				defer atomic.AddInt32(&active, -1)
+
+				func() {
+					m.Lock()
+					defer m.Unlock()
+					if max < na {
+						max = na
+					}
+				}()
+
+				// Sleep a small amount to simulate work. This should be
+				// sufficient to saturate the threadpool before the first
+				// one wakes up.
+				time.Sleep(10 * time.Millisecond)
+				return nil
+			}
+
+			p := New(tc.size)
+			for idx := 0; idx < tc.work; idx++ {
+				wg.Add(1)
+				p.Go(worker)
+			}
+
+			// First wait for the waitgroup to finish, so that
+			// we are sure it isn't the Wait call that flushes
+			// remaining work.
+			wg.Wait()
+
+			if err := p.Wait(); err != nil {
+				t.Errorf("Wait() = %v", err)
+			}
+
+			if err := p.Wait(); err != nil {
+				t.Errorf("Wait() = %v", err)
+			}
+
+			if got, want := max, int32(tc.size); got != want {
+				t.Errorf("max active = %v, wanted %v", got, want)
+			}
+		})
+	}
+}
+
+func TestParallelismWithErrors(t *testing.T) {
+	tests := []struct {
+		name string
+		size int
+		work int
+	}{{
+		name: "single threaded",
+		size: 1,
+		work: 3,
+	}, {
+		name: "three workers",
+		size: 3,
+		work: 10,
+	}, {
+		name: "ten workers",
+		size: 10,
+		work: defaultCapacity + 10,
+	}}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			var (
+				// m guards max.
+				m      sync.Mutex
+				max    int32
+				active int32
+			)
+
+			// Use our own waitgroup to ensure that the work
+			// can all complete before we block on the error
+			// result.
+			wg := &sync.WaitGroup{}
+
+			// The barrier holds all requests we don't expect to
+			// finish until we validate the condition.
+			barrier := make(chan struct{})
+
+			errExpected := errors.New("this is what I expect")
+			workerFactory := func(err error) func() error {
+				return func() error {
+					defer wg.Done()
+					na := atomic.AddInt32(&active, 1)
+					defer atomic.AddInt32(&active, -1)
+
+					func() {
+						m.Lock()
+						defer m.Unlock()
+						if max < na {
+							max = na
+						}
+					}()
+
+					// Sleep a small amount to simulate work. This should be
+					// sufficient to saturate the threadpool before the first
+					// one wakes up.
+					time.Sleep(10 * time.Millisecond)
+
+					// Make all unexpected errors wait.
+					if err != errExpected {
+						<-barrier
+					}
+					return err
+				}
+			}
+
+			p, ctx := NewWithContext(context.Background(), tc.size, defaultCapacity)
+
+			for i := 0; i < tc.work; i++ {
+				err := errors.New("this is not what I expect")
+				if i == tc.size-1 {
+					// Just fit in a request with the error we expect.
+					err = errExpected
+				}
+
+				wg.Add(1)
+				p.Go(workerFactory(err))
+			}
+
+			// Wait for the error to propagate.
+			<-ctx.Done()
+			// Allow all remaining requests to finish.
+			close(barrier)
+
+			// First wait for the waitgroup to finish, so that
+			// we are sure it isn't the Wait call that flushes
+			// remaining work.
+			wg.Wait()
+
+			if err := p.Wait(); err != errExpected {
+				t.Errorf("Wait() = %v, wanted %v", err, errExpected)
+			}
+
+			if got, want := max, int32(tc.size); got != want {
+				t.Errorf("max active = %v, wanted %v", got, want)
+			}
+		})
+	}
+}
+
+func TestWithContextWaitCancels(t *testing.T) {
+	pool, ctx := NewWithContext(context.Background(), 1 /*1 thread*/, 10 /*capacity*/)
+	for i := 0; i < 10; i++ {
+		pool.Go(func() error {
+			time.Sleep(10 * time.Millisecond)
+			return nil
+		})
+	}
+	if err := pool.Wait(); err != nil {
+		t.Fatal("pool.Wait =", err)
+	}
+	select {
+	case <-ctx.Done():
+	default:
+		t.Error("ctx is not canceled")
+	}
+}
+
+func TestErrorCancelsContext(t *testing.T) {
+	want := errors.New("i failed, sorry")
+	pool, ctx := NewWithContext(context.Background(), 1 /*1 thread*/, 10 /*capacity*/)
+	pool.Go(func() error {
+		return want
+	})
+	// Those don't matter, but generate load.
+	for i := 0; i < 10; i++ {
+		pool.Go(func() error {
+			time.Sleep(100 * time.Millisecond)
+			return nil
+		})
+	}
+	// This should be triggered basically immediately.
+	select {
+	case <-ctx.Done():
+	case <-time.After(100 * time.Millisecond):
+		t.Error("ctx is not canceled due to the first error")
+	}
+	if err := pool.Wait(); err != want {
+		t.Fatalf("pool.Wait() = %v, want: %v", err, want)
+	}
+}
diff --git a/reconciler/reconcile_common_test.go b/reconciler/reconcile_common_test.go
index b61cc0399..1981e0815 100644
--- a/reconciler/reconcile_common_test.go
+++ b/reconciler/reconcile_common_test.go
@@ -90,7 +90,7 @@ func TestPostProcessReconcileBumpsGeneration(t *testing.T) {
 		t.Errorf("Expected observed generation bump got=%d want=%d", resource.Status.ObservedGeneration, resource.Generation)
 	}
 
-	if krShape.GetStatus().ObservedGeneration != krShape.GetObjectMeta().GetGeneration() {
+	if krShape.GetStatus().ObservedGeneration != krShape.GetGeneration() {
 		t.Errorf("Expected observed generation bump got=%d want=%d", resource.Status.ObservedGeneration, resource.Generation)
 	}
 }
diff --git a/test/cmd/command_test.go b/test/cmd/command_test.go
index 1cd7a082b..93adb9603 100644
--- a/test/cmd/command_test.go
+++ b/test/cmd/command_test.go
@@ -112,7 +112,7 @@ func TestRunCommands(t *testing.T) {
 			1,
 		},
 		{
-			[]string{"bash -c \"echo foo > /dev/stderr; exit 4\""},
+			[]string{`bash -c "echo foo > /dev/stderr; exit 4"`},
 			"",
 			"foo\n",
 			4,
diff --git a/test/e2e_flags.go b/test/e2e_flags.go
index 3dfb2eb3d..bc6f90bff 100644
--- a/test/e2e_flags.go
+++ b/test/e2e_flags.go
@@ -32,7 +32,6 @@ import (
 )
 
 var (
-	klogFlags = flag.NewFlagSet("klog", flag.ExitOnError)
 	// Flags holds the command line flags or defaults for settings in the user's environment.
 	// See EnvironmentFlags for a list of supported fields.
 	Flags = initializeFlags()
diff --git a/test/ha/ha.go b/test/ha/ha.go
new file mode 100644
index 000000000..26326358f
--- /dev/null
+++ b/test/ha/ha.go
@@ -0,0 +1,45 @@
+/*
+Copyright 2020 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 ha
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/wait"
+	"knative.dev/pkg/test"
+	"knative.dev/pkg/test/logging"
+)
+
+// WaitForNewLeader waits until the holder of the given lease is different from the previousLeader.
+func WaitForNewLeader(client *test.KubeClient, lease, namespace, previousLeader string) (string, error) {
+	span := logging.GetEmitableSpan(context.Background(), "WaitForNewLeader/"+lease)
+	defer span.End()
+	var leader string
+	err := wait.PollImmediate(time.Second, time.Minute, func() (bool, error) {
+		lease, err := client.Kube.CoordinationV1().Leases(namespace).Get(lease, metav1.GetOptions{})
+		if err != nil {
+			return false, fmt.Errorf("error getting lease %s: %w", lease, err)
+		}
+		leader = strings.Split(*lease.Spec.HolderIdentity, "_")[0]
+		return leader != previousLeader, nil
+	})
+	return leader, err
+}
diff --git a/test/interactive/command.go b/test/interactive/command.go
new file mode 100644
index 000000000..edea81ab4
--- /dev/null
+++ b/test/interactive/command.go
@@ -0,0 +1,81 @@
+/*
+Copyright 2020 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.
+*/
+
+// Helper functions for running interactive CLI sessions from Go
+package interactive
+
+import (
+	"io"
+	"log"
+	"os"
+	"os/exec"
+	"strings"
+)
+
+// could be public so extensions of this can test themselves
+var defaultRun func(*exec.Cmd) error
+
+func init() {
+	defaultRun = func(c *exec.Cmd) error { return c.Run() }
+}
+
+// Command represents running an executable, to make it easy to write interactive "shell scripts" in Go.
+// This routes standard in, out, and error from and to the calling program, intended usually to be a login shell run by a person.
+type Command struct {
+	Name string
+	Args []string
+	// If LogFile is set, anything send to stdout and stderr is tee'd to the file named in LogFile when .Run() is called.
+	LogFile string
+	run     func(*exec.Cmd) error
+}
+
+// NewCommand creates an Command, breaking out .Name and .Args for you
+func NewCommand(cmdAndArgs ...string) Command {
+	if len(cmdAndArgs) < 1 {
+		log.Fatal("NewCommand must have a least the command given")
+	}
+	return Command{cmdAndArgs[0], cmdAndArgs[1:], "", defaultRun}
+}
+
+// Run executes the command with exec.Command(), sets standard in/out/err, and returns the result of exec.Cmd.Run()
+// Will tee stdout&err to .LogFile if set
+func (c Command) Run() error {
+	cmd := exec.Command(c.Name, c.Args...)
+	cmd.Stdin = os.Stdin
+	if len(c.LogFile) > 0 {
+		f, err := os.OpenFile(c.LogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
+		if err != nil {
+			log.Fatal(err)
+		}
+		cmd.Stdout = io.MultiWriter(os.Stdout, f)
+		cmd.Stderr = io.MultiWriter(os.Stderr, f)
+	} else {
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+	}
+	return c.run(cmd)
+}
+
+// String conforms to Stringer
+func (c Command) String() string {
+	s := c.Name + " " + strings.Join(c.Args, " ")
+	return "Command to run: " + s
+}
+
+// AddArgs appends arguments to the current list of args
+func (c *Command) AddArgs(args ...string) {
+	c.Args = append(c.Args, args...)
+}
diff --git a/test/interactive/command_test.go b/test/interactive/command_test.go
new file mode 100644
index 000000000..9ff310217
--- /dev/null
+++ b/test/interactive/command_test.go
@@ -0,0 +1,67 @@
+/*
+Copyright 2020 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 interactive
+
+import (
+	"os/exec"
+	"testing"
+
+	"github.com/davecgh/go-spew/spew"
+)
+
+var (
+	mySpew        *spew.ConfigState
+	arbitraryArgs []string
+)
+
+func init() {
+	mySpew = spew.NewDefaultConfig()
+	mySpew.DisableMethods = true
+	arbitraryArgs = []string{"docker", "run", "something"}
+}
+
+func argsValidator(t *testing.T, args []string) func(c *exec.Cmd) error {
+	return func(c *exec.Cmd) error {
+		for i, arg := range arbitraryArgs {
+			if c.Args[i] != arg {
+				t.Errorf("c.Args[%d] not correct", i)
+			}
+		}
+		t.Log("Validated c.Args")
+		if t.Failed() {
+			mySpew.Sdump(*c)
+		}
+		return nil
+	}
+}
+
+func TestNewCommand(t *testing.T) {
+	cmd := NewCommand(arbitraryArgs...)
+	cmd.run = argsValidator(t, arbitraryArgs)
+	cmd.Run()
+}
+
+func TestAddArgs(t *testing.T) {
+	cmd := NewCommand(arbitraryArgs[0])
+	cmd.AddArgs(arbitraryArgs[1:]...)
+	cmd.run = argsValidator(t, arbitraryArgs)
+	cmd.Run()
+}
+
+func TestCommandStringer(t *testing.T) {
+	t.Log(NewCommand("true"))
+}
diff --git a/test/interactive/docker.go b/test/interactive/docker.go
new file mode 100644
index 000000000..08bbbc9c9
--- /dev/null
+++ b/test/interactive/docker.go
@@ -0,0 +1,114 @@
+/*
+Copyright 2020 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.
+*/
+
+// Helper functions for running interactive CLI sessions from Go
+package interactive
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"path"
+	"strings"
+)
+
+var defaultDockerCommands []string
+
+func init() {
+	defaultDockerCommands = []string{"docker", "run", "-it", "--rm", "--entrypoint", "bash"}
+}
+
+// Env represents a collection of environment variables and their values
+type Env map[string]string
+
+// Docker is mostly an Command preloaded with arguments which setup Docker for running an image interactively.
+type Docker struct {
+	Command
+}
+
+// PromoteFromEnv pulls the named environment variables from the environment and puts them in the Env.
+// It does not stop on error and returns an error listing all the failed values
+func (e Env) PromoteFromEnv(envVars ...string) error {
+	var err error
+	for _, env := range envVars {
+		v := os.Getenv(env)
+		if v == "" {
+			err = fmt.Errorf("environment variable %q is not set; %v", env, err)
+		} else {
+			e[env] = v
+		}
+	}
+	return err
+}
+
+// Creates an Docker with default Docker command arguments for running interactively
+func NewDocker() Docker {
+	return Docker{NewCommand(defaultDockerCommands...)}
+}
+
+// AddEnv adds arguments so all the environment variables present in e become part of the docker run's environment
+func (d *Docker) AddEnv(e Env) {
+	for k, v := range e {
+		d.AddArgs("-e", fmt.Sprintf("%s=%s", k, v))
+	}
+}
+
+// AddMount add arguments for the --mount command
+func (d *Docker) AddMount(typeStr, source, target string, optAdditionalArgs ...string) {
+	addl := ""
+	if len(optAdditionalArgs) != 0 {
+		addl = "," + strings.Join(optAdditionalArgs, ",")
+	}
+	d.AddArgs("--mount", fmt.Sprintf("type=%s,source=%s,target=%s%s", typeStr, source, target, addl))
+}
+
+// AddRWOverlay mounts a directory into the image at the desired location, but with an overlay
+//  so internal changes do not modify the external directory.
+// externalDirectory probably needs to be an absolute path
+// Returns a function to clean up the mount (but does not delete the directory).
+// Uses sudo and probably only works on Linux
+func (d *Docker) AddRWOverlay(externalDirectory, internalDirectory string) func() {
+	tmpDir, err := ioutil.TempDir("", "overlay")
+	if err != nil {
+		log.Fatal(err)
+	}
+	subDirs := []string{"upper", "work", "overlay"}
+	for _, d := range subDirs {
+		err = os.Mkdir(path.Join(tmpDir, d), 0777)
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+	overlayDir := path.Join(tmpDir, "overlay")
+	// The options for overlay mount are confusing
+	// You need empty directories for upper and work (and overlay, though you can mount over directories that have files in them if you *want* to...)
+	mount := NewCommand("sudo", "mount", "-t", "overlay", "-o",
+		fmt.Sprintf("lowerdir=%s,upperdir=%s/upper,workdir=%s/work", externalDirectory, tmpDir, tmpDir),
+		"none", overlayDir)
+	// Print command to run so user knows why it is asking for sudo password (if it does)
+	log.Println(mount)
+	if err = mount.Run(); err != nil {
+		log.Fatalf("Unable to create overlay mount, so giving up: %v", err)
+	}
+	d.AddMount("bind", overlayDir, internalDirectory)
+	return func() {
+		// Print command to run so user knows why it is asking for sudo password (if it does)
+		umount := NewCommand("sudo", "umount", overlayDir)
+		log.Println(umount)
+		umount.Run()
+	}
+}
diff --git a/test/interactive/docker_test.go b/test/interactive/docker_test.go
new file mode 100644
index 000000000..29dc297c8
--- /dev/null
+++ b/test/interactive/docker_test.go
@@ -0,0 +1,81 @@
+/*
+Copyright 2020 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 interactive
+
+import (
+	"os"
+	"os/exec"
+	"testing"
+)
+
+func TestEnv(t *testing.T) {
+	e := make(Env)
+	err := e.PromoteFromEnv("PWD")
+	if err != nil {
+		t.Error(err)
+		t.Logf("The Environ is:\n%+v\n", os.Environ())
+	}
+	epwd, exists := e["PWD"]
+	if !exists || epwd != os.Getenv("PWD") {
+		t.Errorf(`$PWD promotion did not occur correctly: Env='%v'; os.Getenv("PWD")=%s`, e, os.Getenv("PWD"))
+	}
+	badName := "GEEZ_I_REALLY_H0PE_TH1S_DOES_NOT_EXIST"
+	// and just in case:
+	for {
+		_, exists := os.LookupEnv(badName)
+		if !exists {
+			break
+		}
+		badName = badName + "z"
+	}
+	err = e.PromoteFromEnv(badName)
+	if err == nil {
+		t.Error("Should have received error promoting non-existent variable")
+	}
+}
+
+func TestAddEnv(t *testing.T) {
+	d := NewDocker()
+	e := make(Env)
+	l := len(defaultDockerCommands)
+	e["env"] = "var"
+	d.AddEnv(e)
+	d.run = func(c *exec.Cmd) error {
+		if c.Args[l] != "-e" || c.Args[l+1] != "env=var" {
+			t.Error("Env var wasn't added correctly")
+			t.Log(mySpew.Sdump(c))
+		}
+		t.Log("Test Over")
+		return nil
+	}
+	d.Run()
+}
+
+func TestAddMount(t *testing.T) {
+	d := NewDocker()
+	l := len(defaultDockerCommands)
+	d.AddMount("bind", "mysource", "mytarget", "other1=banana1", "other2=banana2")
+	d.run = func(c *exec.Cmd) error {
+		if c.Args[l] != "--mount" || c.Args[l+1] != "type=bind,source=mysource,target=mytarget,other1=banana1,other2=banana2" {
+			t.Error("Mount wasn't added correctly")
+			t.Log(mySpew.Sdump(c))
+		}
+		t.Log("Test Over")
+		return nil
+	}
+	d.Run()
+}
diff --git a/test/kube_checks.go b/test/kube_checks.go
index 35bcbd201..61a259ac9 100644
--- a/test/kube_checks.go
+++ b/test/kube_checks.go
@@ -25,8 +25,10 @@ import (
 	"strings"
 	"time"
 
+	"github.com/google/go-cmp/cmp"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
+	apierrs "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/wait"
 	k8styped "k8s.io/client-go/kubernetes/typed/core/v1"
@@ -93,6 +95,19 @@ func WaitForPodState(client *KubeClient, inState func(p *corev1.Pod) (bool, erro
 	})
 }
 
+// WaitForPodDeleted waits for the given pod to disappear from the given namespace.
+func WaitForPodDeleted(client *KubeClient, name, namespace string) error {
+	if err := WaitForPodState(client, func(p *corev1.Pod) (bool, error) {
+		// Always return false. We're oly interested in the error which indicates pod deletion or timeout.
+		return false, nil
+	}, name, namespace); err != nil {
+		if !apierrs.IsNotFound(err) {
+			return err
+		}
+	}
+	return nil
+}
+
 // WaitForServiceHasAtLeastOneEndpoint polls the status of the specified Service
 // from client every interval until number of service endpoints = numOfEndpoints
 func WaitForServiceEndpoints(client *KubeClient, svcName string, svcNamespace string, numOfEndpoints int) error {
@@ -121,6 +136,29 @@ func countEndpointsNum(e *corev1.Endpoints) int {
 	return num
 }
 
+// GetEndpointAddresses returns addresses of endpoints for the given service.
+func GetEndpointAddresses(client *KubeClient, svcName, svcNamespace string) ([]string, error) {
+	endpoints, err := client.Kube.CoreV1().Endpoints(svcNamespace).Get(svcName, metav1.GetOptions{})
+	if err != nil || countEndpointsNum(endpoints) == 0 {
+		return nil, fmt.Errorf("no endpoints or error: %w", err)
+	}
+	var hosts []string
+	for _, sub := range endpoints.Subsets {
+		for _, addr := range sub.Addresses {
+			hosts = append(hosts, addr.IP)
+		}
+	}
+	return hosts, nil
+}
+
+// WaitForChangedEndpoints waits until the endpoints for the given service differ from origEndpoints.
+func WaitForChangedEndpoints(client *KubeClient, svcName, svcNamespace string, origEndpoints []string) error {
+	return wait.PollImmediate(1*time.Second, 2*time.Minute, func() (bool, error) {
+		newEndpoints, err := GetEndpointAddresses(client, svcName, svcNamespace)
+		return !cmp.Equal(origEndpoints, newEndpoints), err
+	})
+}
+
 // GetConfigMap gets the configmaps for a given namespace
 func GetConfigMap(client *KubeClient, namespace string) k8styped.ConfigMapInterface {
 	return client.Kube.CoreV1().ConfigMaps(namespace)
@@ -176,3 +214,17 @@ func PodsRunning(podList *corev1.PodList) (bool, error) {
 func PodRunning(pod *corev1.Pod) bool {
 	return pod.Status.Phase == corev1.PodRunning || pod.Status.Phase == corev1.PodSucceeded
 }
+
+// WaitForDeploymentScale waits until the given deployment has the expected scale.
+func WaitForDeploymentScale(client *KubeClient, name, namespace string, scale int) error {
+	return WaitForDeploymentState(
+		client,
+		name,
+		func(d *appsv1.Deployment) (bool, error) {
+			return d.Status.ReadyReplicas == int32(scale), nil
+		},
+		"DeploymentIsScaled",
+		namespace,
+		time.Minute,
+	)
+}
diff --git a/test/logging/example_tlogger_test.go b/test/logging/example_tlogger_test.go
index 1395822fa..7c8660034 100644
--- a/test/logging/example_tlogger_test.go
+++ b/test/logging/example_tlogger_test.go
@@ -34,8 +34,7 @@ var (
 
 func init() { someStruct = testStruct{"hello", 42.0} }
 
-// godoc limitation; this would be named Test*(), of course
-
+// nolint:govet // godoc limitation; this would be named Test*(), of course
 func Example(legacy *testing.T) {
 	// Get our TLogger and ready the cleanup function
 	t, cancel := logging.NewTLogger(legacy)
diff --git a/test/mako/alerter/slack/message_test.go b/test/mako/alerter/slack/message_test.go
index bf3741689..fd99d5345 100644
--- a/test/mako/alerter/slack/message_test.go
+++ b/test/mako/alerter/slack/message_test.go
@@ -46,7 +46,7 @@ func TestMessaging(t *testing.T) {
 		name string
 	}{
 		{"test name"},
-		{"special name <>&'\""},
+		{`special name <>&'"`},
 	}
 
 	for _, tc := range testCases {
diff --git a/test/prow/prow.go b/test/prow/prow.go
index a6e7f3a50..2af8a0095 100644
--- a/test/prow/prow.go
+++ b/test/prow/prow.go
@@ -34,9 +34,6 @@ import (
 )
 
 const (
-	// OrgName is the name of knative org
-	OrgName = "knative"
-
 	// BucketName is the gcs bucket for all knative builds
 	BucketName = "knative-prow"
 	// Latest is the filename storing latest build number
@@ -71,6 +68,7 @@ type Job struct {
 	Name        string
 	Type        string
 	Bucket      string  // optional
+	Org         string  // optional
 	Repo        string  // optional
 	StoragePath string  // optional
 	PullID      int     // only for Presubmit jobs
@@ -135,11 +133,13 @@ func Initialize(serviceAccount string) error {
 
 // NewJob creates new job struct
 // pullID is only saved by Presubmit job for determining StoragePath
-func NewJob(jobName, jobType, repoName string, pullID int) *Job {
+func NewJob(jobName, jobType, orgName, repoName string, pullID int) *Job {
 	job := Job{
 		Name:   jobName,
 		Type:   jobType,
 		Bucket: BucketName,
+		Org:    orgName,
+		Repo:   repoName,
 	}
 
 	switch jobType {
@@ -147,7 +147,7 @@ func NewJob(jobName, jobType, repoName string, pullID int) *Job {
 		job.StoragePath = path.Join("logs", jobName)
 	case PresubmitJob:
 		job.PullID = pullID
-		job.StoragePath = path.Join("pr-logs", "pull", OrgName+"_"+repoName, strconv.Itoa(pullID), jobName)
+		job.StoragePath = path.Join("pr-logs", "pull", orgName+"_"+repoName, strconv.Itoa(pullID), jobName)
 	case BatchJob:
 		job.StoragePath = path.Join("pr-logs", "pull", "batch", jobName)
 	default:
diff --git a/test/prow/prow_test.go b/test/prow/prow_test.go
index d5c82fbf7..84cde9cf2 100644
--- a/test/prow/prow_test.go
+++ b/test/prow/prow_test.go
@@ -24,6 +24,7 @@ import (
 )
 
 const (
+	orgName        = "test-org"
 	repoName       = "test-repo"
 	invalidJobType = "invalid"
 	testJobName    = "job_0"
@@ -35,13 +36,13 @@ var jobPathTests = []struct {
 }{
 	{PeriodicJob, "logs/job_0"},
 	{PostsubmitJob, "logs/job_0"},
-	{PresubmitJob, "pr-logs/pull/knative_test-repo/0/job_0"},
+	{PresubmitJob, "pr-logs/pull/test-org_test-repo/0/job_0"},
 	{BatchJob, "pr-logs/pull/batch/job_0"},
 }
 
 func TestJobPath(t *testing.T) {
 	for _, testData := range jobPathTests {
-		job := NewJob(testJobName, testData.in, repoName, 0)
+		job := NewJob(testJobName, testData.in, orgName, repoName, 0)
 		if job.StoragePath != testData.out {
 			t.Errorf("Expected '%s', actual '%s'", testData.out, job.StoragePath)
 		}
@@ -58,7 +59,7 @@ func TestInvalidJobPath(t *testing.T) {
 		exitString = expectedExitString
 	}
 
-	NewJob(testJobName, invalidJobType, repoName, 0)
+	NewJob(testJobName, invalidJobType, orgName, repoName, 0)
 	if exitString != expectedExitString {
 		t.Fatalf("Expected: %s, actual: %s", exitString, expectedExitString)
 	}
diff --git a/test/spoof/spoof.go b/test/spoof/spoof.go
index 2778fe9e7..c6e1ffae7 100644
--- a/test/spoof/spoof.go
+++ b/test/spoof/spoof.go
@@ -20,7 +20,9 @@ package spoof
 
 import (
 	"context"
+	"errors"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"net"
 	"net/http"
@@ -244,18 +246,22 @@ func (sc *SpoofingClient) Poll(req *http.Request, inState ResponseChecker, error
 // DefaultErrorRetryChecker implements the defaults for retrying on error.
 func DefaultErrorRetryChecker(err error) (bool, error) {
 	if isTCPTimeout(err) {
-		return true, fmt.Errorf("Retrying for TCP timeout: %w", err)
+		return true, fmt.Errorf("retrying for TCP timeout: %w", err)
 	}
 	// Retrying on DNS error, since we may be using xip.io or nip.io in tests.
 	if isDNSError(err) {
-		return true, fmt.Errorf("Retrying for DNS error: %w", err)
+		return true, fmt.Errorf("retrying for DNS error: %w", err)
 	}
 	// Repeat the poll on `connection refused` errors, which are usually transient Istio errors.
 	if isConnectionRefused(err) {
-		return true, fmt.Errorf("Retrying for connection refused: %w", err)
+		return true, fmt.Errorf("retrying for connection refused: %w", err)
 	}
 	if isConnectionReset(err) {
-		return true, fmt.Errorf("Retrying for connection reset: %w", err)
+		return true, fmt.Errorf("retrying for connection reset: %w", err)
+	}
+	// Retry on connection/network errors.
+	if errors.Is(err, io.EOF) {
+		return true, fmt.Errorf("retrying for: %w", err)
 	}
 	return false, err
 }
diff --git a/test/webhook-apicoverage/resourcetree/test_util.go b/test/webhook-apicoverage/resourcetree/test_util.go
index 34fca0545..3b922d44a 100644
--- a/test/webhook-apicoverage/resourcetree/test_util.go
+++ b/test/webhook-apicoverage/resourcetree/test_util.go
@@ -49,11 +49,13 @@ type arrayType struct {
 	baseArr   []bool
 }
 
+//nolint:structcheck,unused // Disable warnings about unused fields.
 type otherType struct {
 	structMap map[string]baseType
 	baseMap   map[string]string
 }
 
+//nolint:structcheck,unused // Disable warnings about unused fields.
 type combinedNodeType struct {
 	b baseType
 	a arrayType
diff --git a/testing/inner_default_resource_test.go b/testing/inner_default_resource_test.go
index a0c1abcf6..5bed4d9ff 100644
--- a/testing/inner_default_resource_test.go
+++ b/testing/inner_default_resource_test.go
@@ -23,15 +23,15 @@ import (
 
 func TestInnerDefaultResource_Validate(t *testing.T) {
 	r := InnerDefaultResource{}
-	if err := r.Validate(context.TODO()); err != nil {
-		t.Fatalf("Expected no validation errors. Actual %v", err)
+	if err := r.Validate(context.Background()); err != nil {
+		t.Error("Expected no validation errors. Actual:", err)
 	}
 }
 
 func TestInnerDefaultResource_SetDefaults(t *testing.T) {
 	r := InnerDefaultResource{}
-	r.SetDefaults(context.TODO())
+	r.SetDefaults(context.Background())
 	if r.Spec.FieldWithDefault != "I'm a default." {
-		t.Fatalf("Unexpected defaulted value: %v", r.Spec.FieldWithDefault)
+		t.Errorf("Unexpected defaulted value: %v", r.Spec.FieldWithDefault)
 	}
 }
diff --git a/testutils/clustermanager/e2e-tests/common/common_test.go b/testutils/clustermanager/e2e-tests/common/common_test.go
index 0a3ee234e..4f5b507ef 100644
--- a/testutils/clustermanager/e2e-tests/common/common_test.go
+++ b/testutils/clustermanager/e2e-tests/common/common_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package common
 
 import (
+	"errors"
 	"fmt"
 	"reflect"
 	"testing"
@@ -30,23 +31,25 @@ func TestStandardExec(t *testing.T) {
 		expErr error
 	}{
 		{"bash", []string{"-c", "echo foo"}, []byte("foo\n"), nil},
-		{"cmd_not_exist", []string{"-c", "echo"}, []byte{}, fmt.Errorf("exec: \"cmd_not_exist\": executable file not found in $PATH")},
+		{"cmd_not_exist", []string{"-c", "echo"}, []byte{}, errors.New(`exec: "cmd_not_exist": executable file not found in $PATH`)},
 	}
 
 	for _, data := range datas {
-		data := data
-		out, err := StandardExec(data.cmd, data.args...)
-		if err != nil {
-			err = fmt.Errorf(err.Error())
-		}
-		errMsg := fmt.Sprintf("running cmd: '%v', args: '%v'", data.cmd, data.args)
-		if !reflect.DeepEqual(data.expErr, err) {
-			t.Errorf("%s\nerror want: '%v'\nerror got: '%v'", errMsg, err, data.expErr)
-		}
+		t.Run(data.cmd, func(t *testing.T) {
+			out, err := StandardExec(data.cmd, data.args...)
+			errMsg := fmt.Sprintf("running cmd: '%v', args: '%v'", data.cmd, data.args)
+			if (err != nil) != (data.expErr != nil) {
+				t.Fatalf("Error = %v, want: %v", err, data.expErr)
+			} else if err != nil {
+				if err.Error() != data.expErr.Error() {
+					t.Errorf("%s\nerror got:  '%v'\nerror want: '%v'", errMsg, data.expErr, err)
+				}
+			}
 
-		if !reflect.DeepEqual(string(out), string(data.expOut)) {
-			t.Errorf("%s\noutput want: '%v'\noutput got: '%v'", errMsg, string(data.expOut), string(out))
-		}
+			if got, want := string(out), string(data.expOut); got != want {
+				t.Errorf("%s\noutput got:  '%s'\noutput want: '%s'", errMsg, got, want)
+			}
+		})
 	}
 }
 
diff --git a/testutils/metahelper/client/client_test.go b/testutils/metahelper/client/client_test.go
index 71a314dba..abccdf73f 100644
--- a/testutils/metahelper/client/client_test.go
+++ b/testutils/metahelper/client/client_test.go
@@ -111,7 +111,7 @@ func TestSet(t *testing.T) {
 	}{
 		{"sync failed", make(map[string]string), "", "", "", make(map[string]string), true},
 		{"set normal key", make(map[string]string), "{}", "a", "b", map[string]string{"a": "b"}, false},
-		{"override", make(map[string]string), "{\"a\":\"b\"}", "a", "c", map[string]string{"a": "c"}, false},
+		{"override", make(map[string]string), `{"a":"b"}`, "a", "c", map[string]string{"a": "c"}, false},
 		{"ignore old client val", map[string]string{"a": "b"}, "{}", "c", "d", map[string]string{"c": "d"}, false},
 	}
 
@@ -143,9 +143,9 @@ func TestGet(t *testing.T) {
 		expErr      bool
 	}{
 		{"sync failed", make(map[string]string), "", "", make(map[string]string), "", true},
-		{"get normal key", make(map[string]string), "{\"a\":\"b\"}", "a", map[string]string{"a": "b"}, "b", false},
-		{"key not exist", make(map[string]string), "{\"a\":\"b\"}", "c", map[string]string{"a": "b"}, "", true},
-		{"ignore old client val", map[string]string{"a": "c"}, "{\"a\":\"b\"}", "a", map[string]string{"a": "b"}, "b", false},
+		{"get normal key", make(map[string]string), `{"a":"b"}`, "a", map[string]string{"a": "b"}, "b", false},
+		{"key not exist", make(map[string]string), `{"a":"b"}`, "c", map[string]string{"a": "b"}, "", true},
+		{"ignore old client val", map[string]string{"a": "c"}, `{"a":"b"}`, "a", map[string]string{"a": "b"}, "b", false},
 	}
 
 	for _, data := range datas {
diff --git a/tracing/config/tracing_test.go b/tracing/config/tracing_test.go
index 309d5225c..7e1cf30c6 100644
--- a/tracing/config/tracing_test.go
+++ b/tracing/config/tracing_test.go
@@ -257,11 +257,6 @@ func TestNewConfigFailures(t *testing.T) {
 		input: map[string]string{
 			sampleRateKey: "1.01",
 		},
-	}, {
-		name: "debug key not a bool",
-		input: map[string]string{
-			debugKey: "not a bool",
-		},
 	}, {
 		name: "zipkin set without backend",
 		input: map[string]string{
diff --git a/tracker/interface_test.go b/tracker/interface_test.go
index 54553f6b8..2622820f8 100644
--- a/tracker/interface_test.go
+++ b/tracker/interface_test.go
@@ -214,7 +214,7 @@ func TestValidate(t *testing.T) {
 				},
 			},
 		},
-		want: apis.ErrInvalidValue("invalid label key \"a b c\": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')", "selector"),
+		want: apis.ErrInvalidValue(`invalid label key "a b c": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`, "selector"),
 	}}
 
 	for _, test := range tests {
diff --git a/vendor/knative.dev/test-infra/scripts/library.sh b/vendor/knative.dev/test-infra/scripts/library.sh
index 0b3927419..092f39811 100644
--- a/vendor/knative.dev/test-infra/scripts/library.sh
+++ b/vendor/knative.dev/test-infra/scripts/library.sh
@@ -544,7 +544,8 @@ function update_licenses() {
   local dst=$1
   local dir=$2
   shift
-  run_go_tool github.com/google/go-licenses go-licenses save "${dir}" --save_path="${dst}" --force || return 1
+  run_go_tool github.com/google/go-licenses go-licenses save "${dir}" --save_path="${dst}" --force || \
+    { echo "--- FAIL: go-licenses failed to update licenses"; return 1; }
   # Hack to make sure directories retain write permissions after save. This
   # can happen if the directory being copied is a Go module.
   # See https://github.com/google/go-licenses/issues/11
@@ -554,7 +555,8 @@ function update_licenses() {
 # Run go-licenses to check for forbidden licenses.
 function check_licenses() {
   # Check that we don't have any forbidden licenses.
-  run_go_tool github.com/google/go-licenses go-licenses check "${REPO_ROOT_DIR}/..." || return 1
+  run_go_tool github.com/google/go-licenses go-licenses check "${REPO_ROOT_DIR}/..." || \
+    { echo "--- FAIL: go-licenses failed the license check"; return 1; }
 }
 
 # Run the given linter on the given files, checking it exists first.
diff --git a/vendor/knative.dev/test-infra/scripts/presubmit-tests.sh b/vendor/knative.dev/test-infra/scripts/presubmit-tests.sh
index 43a23df31..1435a3469 100644
--- a/vendor/knative.dev/test-infra/scripts/presubmit-tests.sh
+++ b/vendor/knative.dev/test-infra/scripts/presubmit-tests.sh
@@ -302,6 +302,8 @@ function main() {
     kubectl version --client
     echo ">> go version"
     go version
+    echo ">> go env"
+    go env
     echo ">> python3 version"
     python3 --version
     echo ">> git version"
diff --git a/vendor/knative.dev/test-infra/scripts/shellcheck-presubmit.sh b/vendor/knative.dev/test-infra/scripts/shellcheck-presubmit.sh
index ce1515bd3..d9c48129a 100644
--- a/vendor/knative.dev/test-infra/scripts/shellcheck-presubmit.sh
+++ b/vendor/knative.dev/test-infra/scripts/shellcheck-presubmit.sh
@@ -1,5 +1,19 @@
 #!/usr/bin/env bash
 
+# Copyright 2020 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.
+
 set -e
 
 source "$(dirname "${BASH_SOURCE[0]}")/library.sh"
diff --git a/webhook/env_test.go b/webhook/env_test.go
new file mode 100644
index 000000000..4ffc8956c
--- /dev/null
+++ b/webhook/env_test.go
@@ -0,0 +1,83 @@
+/*
+Copyright 2020 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 webhook
+
+import (
+	"os"
+	"testing"
+)
+
+const (
+	testDefaultPort      = 8888
+	testMissingInputName = "MissingInput" // portEnvKey is not found.
+)
+
+type portTest struct {
+	name      string
+	in        string
+	want      int
+	wantPanic bool
+}
+
+func TestPort(t *testing.T) {
+	tests := []portTest{{
+		name: testMissingInputName,
+		want: testDefaultPort,
+	}, {
+		name: "EmptyInput",
+		in:   "",
+		want: testDefaultPort,
+	}, {
+		name:      "InvalidInputNonNumeric",
+		in:        "invalid",
+		wantPanic: true,
+	}, {
+		name:      "InvalidInputTrailingSpace",
+		in:        "8443 ",
+		wantPanic: true,
+	}, {
+		name:      "InvalidInputZero",
+		in:        "0",
+		wantPanic: true,
+	}, {
+		name: "ValidInput",
+		in:   "443",
+		want: 443,
+	}}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			// portEnvKey is unset when testing missing input.
+			if tc.name != testMissingInputName {
+				os.Setenv(portEnvKey, tc.in)
+			}
+
+			defer func() {
+				if r := recover(); r == nil && tc.wantPanic {
+					t.Error("Did not panic")
+				} else if r != nil && !tc.wantPanic {
+					t.Error("Got unexpected panic")
+				}
+				os.Unsetenv(portEnvKey)
+			}()
+
+			if got := PortFromEnv(testDefaultPort); got != tc.want {
+				t.Errorf("PortFromEnv = %d, want: %d", got, tc.want)
+			}
+		})
+	}
+}
diff --git a/webhook/webhook_test.go b/webhook/webhook_test.go
index 3f2b29ed5..bcdef62d9 100644
--- a/webhook/webhook_test.go
+++ b/webhook/webhook_test.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"net"
 	"testing"
+	"time"
 
 	"golang.org/x/sync/errgroup"
 	"knative.dev/pkg/controller"
@@ -44,6 +45,11 @@ const (
 	user1            = "brutto@knative.dev"
 )
 
+func init() {
+	// Don't hang forever when running tests.
+	GracePeriod = 100 * time.Millisecond
+}
+
 func newNonRunningTestWebhook(t *testing.T, options Options, acs ...interface{}) (
 	ctx context.Context, ac *Webhook, cancel context.CancelFunc) {
 	t.Helper()