#!/bin/bash # # This script runs Go language unit tests for the Origin repository. Arguments to this script # are parsed as a list of packages to test until the first argument starting with '-' or '--' is # found. That argument and all following arguments are interpreted as flags to be passed directly # to `go test`. If no arguments are given, then "all" packages are tested. # # Coverage reports and jUnit XML reports can be generated by this script as well, but both cannot # be generated at once. # # This script consumes the following parameters as environment variables: # - DRY_RUN: prints all packages that would be tested with the args that would be used and exits # - TEST_KUBE: toggles testing of non-essential Kubernetes unit tests # - TIMEOUT: the timeout for any one unit test (default '60s') # - DETECT_RACES: toggles the 'go test' race detector (defaults '-race') # - COVERAGE_OUTPUT_DIR: locates the directory in which coverage output files will be placed # - COVERAGE_SPEC: a set of flags for 'go test' that specify the coverage behavior (default '-cover -covermode=atomic') # - GOTEST_FLAGS: any other flags to be sent to 'go test' # - JUNIT_REPORT: toggles the creation of jUnit XML from the test output and changes this script's output behavior # to use the 'junitreport' tool for summarizing the tests. # - DLV_DEBUG toggles running tests using delve debugger function exit_trap() { local return_code=$? end_time=$(date +%s) if [[ "${return_code}" -eq "0" ]]; then verb="succeeded" else verb="failed" fi echo "$0 ${verb} after $(( end_time - start_time )) seconds" exit "${return_code}" } trap exit_trap EXIT start_time=$(date +%s) source "$(dirname "${BASH_SOURCE}")/lib/init.sh" os::build::setup_env os::util::environment::setup_tmpdir_vars "test-go" # TODO(skuznets): remove these once we've migrated all tools to the new vars if [[ -n "${KUBE_TIMEOUT+x}" ]]; then TIMEOUT="${KUBE_TIMEOUT}" os::log::warn "The flag \$KUBE_TIMEOUT for $0 is deprecated, use \$TIMEOUT instead." fi if [[ -n "${KUBE_COVER+x}" ]]; then COVERAGE_SPEC="${KUBE_COVER}" os::log::warn "The flag \$KUBE_COVER for $0 is deprecated, use \$COVERAGE_SPEC instead." fi if [[ -n "${OUTPUT_COVERAGE+x}" ]]; then COVERAGE_OUTPUT_DIR="${OUTPUT_COVERAGE}" os::log::warn "The flag \$OUTPUT_COVERAGE for $0 is deprecated, use \$COVERAGE_OUTPUT_DIR instead." fi if [[ -n "${KUBE_RACE+x}" ]]; then DETECT_RACES="${KUBE_RACE}" os::log::warn "The flag \$KUBE_RACE for $0 is deprecated, use \$DETECT_RACES instead." fi if [[ -n "${PRINT_PACKAGES+x}" ]]; then DRY_RUN="${PRINT_PACKAGES}" os::log::warn "The flag \$PRINT_PACKAGES for $0 is deprecated, use \$DRY_RUN instead." fi # Internalize environment variables we consume and default if they're not set dry_run="${DRY_RUN:-}" test_kube="${TEST_KUBE:-}" test_timeout="${TIMEOUT:-120s}" detect_races="${DETECT_RACES:-true}" coverage_output_dir="${COVERAGE_OUTPUT_DIR:-}" coverage_spec="${COVERAGE_SPEC:--cover -covermode atomic}" gotest_flags="${GOTEST_FLAGS:-}" junit_report="${JUNIT_REPORT:-}" dlv_debug="${DLV_DEBUG:-}" if [[ -n "${junit_report}" && -n "${coverage_output_dir}" ]]; then echo "$0 cannot create jUnit XML reports and coverage reports at the same time." exit 1 fi # determine if user wanted verbosity verbose= if [[ "${gotest_flags}" =~ -v( |$) ]]; then verbose=true fi # Build arguments for 'go test' if [[ -z "${verbose}" && -n "${junit_report}" ]]; then # verbosity can be set explicitly by the user or set implicitly by asking for the jUnit # XML report, so we only want to add the flag if it hasn't been added by a user already # and is being implicitly set by jUnit report generation gotest_flags+=" -v" fi if [[ "${detect_races}" == "true" ]]; then gotest_flags+=" -race" fi # check to see if user has not disabled coverage mode if [[ -n "${coverage_spec}" ]]; then # if we have a coverage spec set, we add it. '-race' implies '-cover -covermode atomic' # but specifying both at the same time does not lead to an error so we can add both specs gotest_flags+=" ${coverage_spec}" fi # check to see if user has not disabled test timeouts if [[ -n "${test_timeout}" ]]; then gotest_flags+=" -timeout ${test_timeout}" fi # list_test_packages_under lists all packages containing Golang test files that we want to run as unit tests # under the given base dir in the OpenShift Origin tree function list_test_packages_under() { local basedir=$* # we do not quote ${basedir} to allow for multiple arguments to be passed in as well as to allow for # arguments that use expansion, e.g. paths containing brace expansion or wildcards find ${basedir} -not \( \ \( \ -path 'vendor' \ -o -path '*_output' \ -o -path '*.git' \ -o -path '*openshift.local.*' \ -o -path '*vendor/*' \ -o -path '*assets/node_modules' \ -o -path '*test/*' \ \) -prune \ \) -name '*_test.go' | xargs -n1 dirname | sort -u | xargs -n1 printf "${OS_GO_PACKAGE}/%s\n" } # Break up the positional arguments into packages that need to be tested and arguments that need to be passed to `go test` package_args= for arg in "$@"; do if [[ "${arg}" =~ -.* ]]; then # we found an arg that begins with a dash, so we stop interpreting arguments # henceforth as packages and instead interpret them as flags to give to `go test` break fi # an arg found before the first flag is a package package_args+=" ${arg}" shift done gotest_flags+=" $*" # Determine packages to test godeps_package_prefix="vendor/" test_packages= if [[ -n "${package_args}" ]]; then for package in ${package_args}; do # If we're trying to recursively test a package under Godeps, strip the Godeps prefix so go test can find the packages correctly if [[ "${package}" == "${godeps_package_prefix}"*"/..." ]]; then test_packages="${test_packages} ${package:${#godeps_package_prefix}}" else test_packages="${test_packages} ${OS_GO_PACKAGE}/${package}" fi done else # If no packages are given to test, we need to generate a list of all packages with unit tests openshift_test_packages="$(list_test_packages_under '*')" kubernetes_path="vendor/k8s.io/kubernetes" mandatory_kubernetes_packages="./vendor/k8s.io/kubernetes/pkg/api ./vendor/k8s.io/kubernetes/pkg/api/v1" test_packages="${openshift_test_packages} ${mandatory_kubernetes_packages}" if [[ -n "${test_kube}" ]]; then # we need to find all of the kubernetes test suites, excluding those we directly whitelisted before, the end-to-end suite, and # the go2idl tests which we currently do not support # etcd3 isn't supported yet and that test flakes upstream optional_kubernetes_packages="$(find "${kubernetes_path}" -not \( \ \( \ -path "${kubernetes_path}/pkg/api" \ -o -path "${kubernetes_path}/pkg/api/v1" \ -o -path "${kubernetes_path}/test" \ -o -path "${kubernetes_path}/cmd/libs/go2idl/client-gen/testoutput/testgroup/unversioned" \ -o -path "${kubernetes_path}/pkg/storage/etcd3" \ -o -path "${kubernetes_path}/third_party/golang/go/build" \ \) -prune \ \) -name '*_test.go' | cut -f 2- -d / | xargs -n1 dirname | sort -u | xargs -n1 printf "./vendor/%s\n")" test_packages="${test_packages} ${optional_kubernetes_packages}" fi fi if [[ -n "${dry_run}" ]]; then echo "The following base flags for \`go test\` will be used by $0:" echo "go test ${gotest_flags}" echo "The following packages will be tested by $0:" for package in ${test_packages}; do echo "${package}" done exit 0 fi # Run 'go test' with the accumulated arguments and packages: if [[ -n "${junit_report}" ]]; then # we need to generate jUnit xml os::util::ensure::built_binary_exists 'junitreport' test_output_file="${LOG_DIR}/test-go.log" test_error_file="${LOG_DIR}/test-go-err.log" junit_report_file="${ARTIFACT_DIR}/report.xml" os::log::info "Running \`go test\`..." # we don't care if the `go test` fails in this pipe, as we want to generate the report and summarize the output anyway set +o pipefail go test -i ${gotest_flags} ${test_packages} go test ${gotest_flags} ${test_packages} 2>"${test_error_file}" \ | tee "${test_output_file}" \ | junitreport --type gotest \ --suites nested \ --roots github.com/openshift/origin \ --stream \ --output "${junit_report_file}" test_return_code="${PIPESTATUS[0]}" set -o pipefail echo summary="$( junitreport summarize < "${junit_report_file}" )" echo "${summary}" if echo "${summary}" | grep -q ', 0 failed,'; then if [[ "${test_return_code}" -ne "0" ]]; then os::log::warn "While the jUnit report found no failed tests, the \`go test\` process failed." os::log::warn "This usually means that the unit test suite failed to compile." fi fi if [[ -s "${test_error_file}" ]]; then os::log::warn "\`go test\` had the following output to stderr:" cat "${test_error_file}" fi if grep -q 'WARNING: DATA RACE' "${test_output_file}"; then locations=( $( sed -n '/WARNING: DATA RACE/=' "${test_output_file}") ) if [[ "${#locations[@]}" -gt 1 ]]; then os::log::warn "\`go test\` detected data races." os::log::warn "Details can be found in the full output file at lines ${locations[*]}." else os::log::warn "\`go test\` detected a data race." os::log::warn "Details can be found in the full output file at line ${locations[*]}." fi fi os::log::info "Full output from \`go test\` logged at ${test_output_file}" os::log::info "jUnit XML report placed at ${junit_report_file}" exit "${test_return_code}" elif [[ -n "${coverage_output_dir}" ]]; then # we need to generate coverage reports go test -i ${gotest_flags} ${test_packages} for test_package in ${test_packages}; do mkdir -p "${coverage_output_dir}/${test_package}" local_gotest_flags="${gotest_flags} -coverprofile=${coverage_output_dir}/${test_package}/profile.out" go test ${local_gotest_flags} ${test_package} done # assemble all profiles and generate a coverage report echo 'mode: atomic' > "${coverage_output_dir}/profiles.out" find "${coverage_output_dir}" -name profile.out | xargs sed '/^mode: atomic$/d' >> "${coverage_output_dir}/profiles.out" go tool cover "-html=${coverage_output_dir}/profiles.out" -o "${coverage_output_dir}/coverage.html" os::log::info "Coverage profile written to ${coverage_output_dir}/coverage.html" # clean up all of the individual coverage reports as they have been subsumed into the report at ${coverage_output_dir}/coverage.html # we can clean up all of the coverage reports at once as they all exist in subdirectories of ${coverage_output_dir}/${OS_GO_PACKAGE} # and they are the only files found in those subdirectories rm -rf "${coverage_output_dir:?}/${OS_GO_PACKAGE}" elif [[ -n "${dlv_debug}" ]]; then # run tests using delve debugger dlv test ${test_packages} else # we need to generate neither jUnit XML nor coverage reports go test -i ${gotest_flags} ${test_packages} go test ${gotest_flags} ${test_packages} fi