Build relevant binaries with coverage.

This commit is contained in:
Katharine Berry 2018-08-24 15:03:03 -07:00
parent 1b3a2dd083
commit aa2a7d001a
2 changed files with 140 additions and 12 deletions

View File

@ -211,6 +211,15 @@ readonly KUBE_STATIC_LIBRARIES=(
kubectl
)
# Fully-qualified package names that we want to instrument for coverage information.
readonly KUBE_COVERAGE_INSTRUMENTED_PACKAGES=(
k8s.io/kubernetes/cmd/kube-apiserver
k8s.io/kubernetes/cmd/kube-controller-manager
k8s.io/kubernetes/cmd/kube-scheduler
k8s.io/kubernetes/cmd/kube-proxy
k8s.io/kubernetes/cmd/kubelet
)
# KUBE_CGO_OVERRIDES is a space-separated list of binaries which should be built
# with CGO enabled, assuming CGO is supported on the target platform.
# This overrides any entry in KUBE_STATIC_LIBRARIES.
@ -441,6 +450,104 @@ kube::golang::outfile_for_binary() {
echo "${output_path}/${bin}"
}
# Argument: the name of a Kubernetes package.
# Returns 0 if the binary can be built with coverage, 0 otherwise.
# NB: this ignores whether coverage is globally enabled or not.
kube::golang::is_covered_binary() {
return $(kube::util::array_contains "$1" "${KUBE_COVERAGE_INSTRUMENTED_PACKAGES[@]}")
}
# Argument: the name of a Kubernetes package (e.g. k8s.io/kubernetes/cmd/kube-scheduler)
# Echos the path to a dummy test used for coverage information.
kube::golang::path_for_coverage_dummy_test() {
local package=$1
local path="${KUBE_GOPATH}/src/$package"
local name=$(basename "$package")
echo "$path/zz_autogenerated_${name}_test.go"
}
# Argument: the name of a Kubernetes package (e.g. k8s.io/kubernetes/cmd/kube-scheduler).
# Creates a dummy unit test on disk in the source directory for the given package.
# This unit test will invoke the package's standard entry point when run.
kube::golang::create_coverage_dummy_test() {
local package="$1"
local name="$(basename "$package")"
cat <<EOF > $(kube::golang::path_for_coverage_dummy_test "$package")
package main
import (
"flag"
"os"
"strconv"
"testing"
"time"
)
func TestMain(m *testing.M) {
// We need to pass coverage instructions to the unittest framework, so we hijack os.Args
original_args := os.Args
now := time.Now().UnixNano()
test_args := []string{os.Args[0], "-test.coverprofile=/tmp/k8s-${name}-" + strconv.FormatInt(now, 10) + ".cov"}
os.Args = test_args
// This is sufficient for the unit tests to be set up.
flag.Parse()
// Restore the original args for use by the program.
os.Args = original_args
// 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 be sure to call this from inside the binary too.
// TODO: actually have some code here.
}
EOF
}
# Argument: the name of a Kubernetes package (e.g. k8s.io/kubernetes/cmd/kube-scheduler).
# Deletes a test generated by kube::golang::create_coverage_dumy_test.
kube::golang::delete_coverage_dummy_test() {
local package=$1
rm $(kube::golang::path_for_coverage_dummy_test "$package")
}
# Arguments: a list of kubernetes packages to build.
# Expected variables: build_args should be set to an array of Go build arguments.
# In addition, the standard build globals are assumed.
#
# Invokes Go to actually build some packages. If coverage is disabled, simply invokes
# go install. If coverage is enabled, builds covered binaries using go test, temporarily
# producing the required unit test files and then cleaning up after itself.
# Non-covered binaries are then built using go install as usual.
kube::golang::build_some_binaries() {
if [[ -n "${build_with_coverage:-}" ]]; then
local -a uncovered=()
for package in "$@"; do
if kube::golang::is_covered_binary "$package"; then
V=2 kube::log::info "Building $package with coverage..."
kube::golang::create_coverage_dummy_test "$package"
go test -c -o "$(kube::golang::outfile_for_binary "$package" "$platform")" \
-covermode count \
-coverpkg k8s.io/... \
"${build_args[@]}" \
"$package"
kube::golang::delete_coverage_dummy_test "$package"
else
uncovered+=("$package")
fi
done
if [[ "${#uncovered[@]}" != 0 ]]; then
V=2 kube::log::info "Building ${uncovered[@]} without coverage..."
go install "${build_args[@]}" "${uncovered[@]}"
else
V=2 kube::log::info "Nothing to build without coverage."
fi
else
V=2 kube::log::info "Coverage is disabled."
go install "${build_args[@]}" "$@"
fi
}
kube::golang::build_binaries_for_platform() {
local platform=$1
@ -460,18 +567,24 @@ kube::golang::build_binaries_for_platform() {
fi
done
local -a build_args
if [[ "${#statics[@]}" != 0 ]]; then
CGO_ENABLED=0 go install -installsuffix static "${goflags[@]:+${goflags[@]}}" \
-gcflags "${gogcflags}" \
-ldflags "${goldflags}" \
"${statics[@]:+${statics[@]}}"
build_args=(
-installsuffix static
${goflags:+"${goflags[@]}"}
-gcflags "${gogcflags:-}"
-ldflags "${goldflags:-}"
)
CGO_ENABLED=0 kube::golang::build_some_binaries "${statics[@]}"
fi
if [[ "${#nonstatics[@]}" != 0 ]]; then
go install "${goflags[@]:+${goflags[@]}}" \
-gcflags "${gogcflags}" \
-ldflags "${goldflags}" \
"${nonstatics[@]:+${nonstatics[@]}}"
build_args=(
${goflags:+"${goflags[@]}"}
-gcflags "${gogcflags:-}"
-ldflags "${goldflags:-}"
)
kube::golang::build_some_binaries "${nonstatics[@]}"
fi
for test in "${tests[@]:+${tests[@]}}"; do
@ -480,9 +593,9 @@ kube::golang::build_binaries_for_platform() {
mkdir -p "$(dirname ${outfile})"
go test -c \
"${goflags[@]:+${goflags[@]}}" \
-gcflags "${gogcflags}" \
-ldflags "${goldflags}" \
${goflags:+"${goflags[@]}"} \
-gcflags "${gogcflags:-}" \
-ldflags "${goldflags:-}" \
-o "${outfile}" \
"${testpkg}"
done
@ -535,10 +648,11 @@ kube::golang::build_binaries() {
host_platform=$(kube::golang::host_platform)
# Use eval to preserve embedded quoted strings.
local goflags goldflags gogcflags
local goflags goldflags gogcflags build_with_coverage
eval "goflags=(${GOFLAGS:-})"
goldflags="${GOLDFLAGS:-} $(kube::version::ldflags)"
gogcflags="${GOGCFLAGS:-}"
build_with_coverage="${KUBE_BUILD_WITH_COVERAGE:-}"
local -a targets=()
local arg

View File

@ -18,6 +18,20 @@ kube::util::sortable_date() {
date "+%Y%m%d-%H%M%S"
}
# arguments: target, item1, item2, item3, ...
# returns 0 if target is in the given items, 1 otherwise.
kube::util::array_contains() {
local search="$1"
local element
shift
for element; do
if [[ "$element" == "$search" ]]; then
return 0
fi
done
return 1
}
kube::util::wait_for_url() {
local url=$1
local prefix=${2:-}