diff --git a/Makefile b/Makefile index c2f591caf..82d3893ad 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,8 @@ APP_VERSION := canary HACK_DIR ?= hack SKIP_GLOBALS := false -GINKGO_SKIP := +# Skip Venafi tests whilst there are issues with the TPP server +GINKGO_SKIP := Venafi GINKGO_FOCUS := ## e2e test vars diff --git a/cmd/acmesolver/BUILD.bazel b/cmd/acmesolver/BUILD.bazel index b3b90a1be..a3e9d882b 100644 --- a/cmd/acmesolver/BUILD.bazel +++ b/cmd/acmesolver/BUILD.bazel @@ -1,5 +1,5 @@ load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") -load("//hack/build:docker.bzl", "image") +load("//hack/build:docker.bzl", "covered_image", "image") image( name = "image", @@ -8,6 +8,12 @@ image( visibility = ["//visibility:public"], ) +covered_image( + name = "image.covered", + component = "acmesolver", + visibility = ["//visibility:public"], +) + go_library( name = "go_default_library", srcs = ["main.go"], diff --git a/cmd/cainjector/BUILD.bazel b/cmd/cainjector/BUILD.bazel index 8023154c2..8c7a2e600 100644 --- a/cmd/cainjector/BUILD.bazel +++ b/cmd/cainjector/BUILD.bazel @@ -1,5 +1,5 @@ load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") -load("//hack/build:docker.bzl", "image") +load("//hack/build:docker.bzl", "covered_image", "image") image( name = "image", @@ -8,6 +8,12 @@ image( visibility = ["//visibility:public"], ) +covered_image( + name = "image.covered", + component = "cainjector", + visibility = ["//visibility:public"], +) + go_library( name = "go_default_library", srcs = [ diff --git a/cmd/controller/BUILD.bazel b/cmd/controller/BUILD.bazel index c08ae7267..186de1c63 100644 --- a/cmd/controller/BUILD.bazel +++ b/cmd/controller/BUILD.bazel @@ -1,5 +1,5 @@ load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") -load("//hack/build:docker.bzl", "image") +load("//hack/build:docker.bzl", "covered_image", "image") image( name = "image", @@ -8,6 +8,12 @@ image( visibility = ["//visibility:public"], ) +covered_image( + name = "image.covered", + component = "controller", + visibility = ["//visibility:public"], +) + go_library( name = "go_default_library", srcs = [ diff --git a/cmd/controller/app/controller.go b/cmd/controller/app/controller.go index c5565e171..ad44961f9 100644 --- a/cmd/controller/app/controller.go +++ b/cmd/controller/app/controller.go @@ -135,7 +135,6 @@ func Run(opts *options.ControllerOptions, stopCh <-chan struct{}) { } startLeaderElection(rootCtx, opts, leaderElectionClient, ctx.Recorder, run) - panic("unreachable") } func buildControllerContext(ctx context.Context, stopCh <-chan struct{}, opts *options.ControllerOptions) (*controller.Context, *rest.Config, error) { @@ -270,7 +269,7 @@ func startLeaderElection(ctx context.Context, opts *options.ControllerOptions, l } // Try and become the leader and start controller manager loops - leaderelection.RunOrDie(context.TODO(), leaderelection.LeaderElectionConfig{ + leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{ Lock: &rl, LeaseDuration: opts.LeaderElectionLeaseDuration, RenewDeadline: opts.LeaderElectionRenewDeadline, diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 2f0d303c0..bf8b01659 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -37,7 +37,7 @@ func main() { flag.CommandLine.Parse([]string{}) if err := cmd.Execute(); err != nil { - klog.Fatal(err) + klog.Info(err) } } diff --git a/cmd/webhook/BUILD.bazel b/cmd/webhook/BUILD.bazel index 399d3f413..6c1099ec3 100644 --- a/cmd/webhook/BUILD.bazel +++ b/cmd/webhook/BUILD.bazel @@ -1,5 +1,5 @@ load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") -load("//hack/build:docker.bzl", "image") +load("//hack/build:docker.bzl", "covered_image", "image") image( name = "image", @@ -8,6 +8,12 @@ image( visibility = ["//visibility:public"], ) +covered_image( + name = "image.covered", + component = "webhook", + visibility = ["//visibility:public"], +) + go_library( name = "go_default_library", srcs = ["main.go"], diff --git a/hack/build/docker.bzl b/hack/build/docker.bzl index a3c098e3b..ad4af395b 100644 --- a/hack/build/docker.bzl +++ b/hack/build/docker.bzl @@ -15,6 +15,7 @@ load("@io_bazel_rules_docker//container:image.bzl", "container_image") load("@io_bazel_rules_docker//container:bundle.bzl", "container_bundle") load("@io_bazel_rules_docker//go:image.bzl", "go_image") +load("@io_bazel_rules_go//go:def.bzl", "go_test") def image( name, @@ -22,12 +23,14 @@ def image( binary, user = "1000", stamp = True, + testonly = False, **kwargs): go_image( name = "%s.app" % name, base = "@static_base//image", binary = binary, + testonly = testonly, ) container_image( @@ -35,6 +38,7 @@ def image( base = "%s.app" % name, user = user, stamp = stamp, + testonly = testonly, **kwargs) container_bundle( @@ -42,4 +46,47 @@ def image( images = { component + ":{STABLE_APP_GIT_COMMIT}": ":" + name, }, + testonly = testonly, ) + +def covered_image(name, component, **kwargs): + native.genrule( + name = "%s.covered-testfile" % name, + cmd = """ +name="%s"; +cat < "$@" +package main +import ( + "testing" + "github.com/jetstack/cert-manager/pkg/util/coverage" +) +func TestMain(m *testing.M) { + // Get coverage running + coverage.InitCoverage("$${name}") + // Go! + main() + // Make sure we actually write the profiling information to disk, if we make it here. + // On long-running services, or anything that calls os.Exit(), this is insufficient, + // so we also flush periodically with a default period of five seconds (configurable by + // the COVERAGE_FLUSH_INTERVAL environment variable). + coverage.FlushCoverage() +} +EOF + """ % component, + outs = ["main_test.go"], + ) + + go_test( + name = "%s.covered-app" % name, + srcs = ["main_test.go"], + embed = [":go_default_library"], + deps = ["//pkg/util/coverage:go_default_library"], + tags = ["manual"], + ) + + image( + name = name, + binary = "%s.covered-app" % name, + testonly = True, + component = component, + **kwargs) diff --git a/pkg/util/BUILD.bazel b/pkg/util/BUILD.bazel index 873424b61..f7a1c5afc 100644 --- a/pkg/util/BUILD.bazel +++ b/pkg/util/BUILD.bazel @@ -37,6 +37,7 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", + "//pkg/util/coverage:all-srcs", "//pkg/util/errors:all-srcs", "//pkg/util/feature:all-srcs", "//pkg/util/kube:all-srcs", diff --git a/pkg/util/coverage/BUILD.bazel b/pkg/util/coverage/BUILD.bazel new file mode 100644 index 000000000..554893776 --- /dev/null +++ b/pkg/util/coverage/BUILD.bazel @@ -0,0 +1,29 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "coverage.go", + "faketestdeps.go", + ], + importpath = "github.com/jetstack/cert-manager/pkg/util/coverage", + visibility = ["//visibility:public"], + deps = [ + "@io_k8s_apimachinery//pkg/util/wait:go_default_library", + "@io_k8s_klog//:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/util/coverage/coverage.go b/pkg/util/coverage/coverage.go new file mode 100644 index 000000000..e212916fe --- /dev/null +++ b/pkg/util/coverage/coverage.go @@ -0,0 +1,90 @@ +/* +Copyright 2019 The Jetstack cert-manager contributors. + +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 coverage provides tools for coverage-instrumented binaries to collect and +// flush coverage information. +package coverage + +import ( + "flag" + "fmt" + "os" + "testing" + "time" + + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/klog" +) + +var coverageFile string + +// tempCoveragePath returns a temporary file to write coverage information to. +// The file is in the same directory as the destination, ensuring os.Rename will work. +func tempCoveragePath() string { + return coverageFile + ".tmp" +} + +// InitCoverage is called from the dummy unit test to prepare Go's coverage framework. +// Clients should never need to call it. +func InitCoverage(name string) { + // We read the coverage destination in from the COVERPROFILE env var, + // or if it's empty we just use a default in /tmp + coverageFile = os.Getenv("COVERPROFILE") + if coverageFile == "" { + coverageFile = "/tmp/cm-" + name + ".cov" + } + fmt.Println("Dumping coverage information to " + coverageFile) + + flushInterval := 5 * time.Second + requestedInterval := os.Getenv("COVERAGE_FLUSH_INTERVAL") + if requestedInterval != "" { + if duration, err := time.ParseDuration(requestedInterval); err == nil { + flushInterval = duration + } else { + panic("Invalid COVERAGE_FLUSH_INTERVAL value; try something like '30s'.") + } + } + + // Set up the unit test framework with the required arguments to activate test coverage. + flag.CommandLine.Parse([]string{"-test.coverprofile", tempCoveragePath()}) + + // Begin periodic logging + go wait.Forever(FlushCoverage, flushInterval) +} + +// FlushCoverage flushes collected coverage information to disk. +// The destination file is configured at startup and cannot be changed. +// Calling this function also sends a line like "coverage: 5% of statements" to stdout. +func FlushCoverage() { + // We're not actually going to run any tests, but we need Go to think we did so it writes + // coverage information to disk. To achieve this, we create a bunch of empty test suites and + // have it "run" them. + tests := []testing.InternalTest{} + benchmarks := []testing.InternalBenchmark{} + examples := []testing.InternalExample{} + + var deps fakeTestDeps + + dummyRun := testing.MainStart(deps, tests, benchmarks, examples) + dummyRun.Run() + + // Once it writes to the temporary path, we move it to the intended path. + // This gets us atomic updates from the perspective of another process trying to access + // the file. + if err := os.Rename(tempCoveragePath(), coverageFile); err != nil { + klog.Errorf("Couldn't move coverage file from %s to %s", coverageFile, tempCoveragePath()) + } +} diff --git a/pkg/util/coverage/faketestdeps.go b/pkg/util/coverage/faketestdeps.go new file mode 100644 index 000000000..51c1010f8 --- /dev/null +++ b/pkg/util/coverage/faketestdeps.go @@ -0,0 +1,54 @@ +/* +Copyright 2019 The Jetstack cert-manager contributors. + +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 coverage + +import ( + "io" +) + +// This is an implementation of testing.testDeps. It doesn't need to do anything, because +// no tests are actually run. It does need a concrete implementation of at least ImportPath, +// which is called unconditionally when running tests. +type fakeTestDeps struct{} + +func (fakeTestDeps) ImportPath() string { + return "" +} + +func (fakeTestDeps) MatchString(pat, str string) (bool, error) { + return false, nil +} + +func (fakeTestDeps) StartCPUProfile(io.Writer) error { + return nil +} + +func (fakeTestDeps) StopCPUProfile() {} + +func (fakeTestDeps) StartTestLog(io.Writer) {} + +func (fakeTestDeps) StopTestLog() error { + return nil +} + +func (fakeTestDeps) WriteHeapProfile(io.Writer) error { + return nil +} + +func (fakeTestDeps) WriteProfileTo(string, io.Writer, int) error { + return nil +}