diff --git a/.travis.yml b/.travis.yml index 9378a58f4..ecfffe14e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -66,11 +66,10 @@ before_install: - if [[ $TRAVIS_GO_VERSION == 1.* ]]; then docker pull gcr.io/etcd-development/etcd-test:go${TRAVIS_GO_VERSION}; fi install: - - go get -t -v -d ./... - - (cd tests && go get -t -v -d ./...) - - (cd pkg && go get -t -v -d ./...) + - date script: + - date - echo "TRAVIS_GO_VERSION=${TRAVIS_GO_VERSION}" - > case "${TARGET}" in diff --git a/Makefile b/Makefile index b3bde92e0..cc12af5bf 100644 --- a/Makefile +++ b/Makefile @@ -177,7 +177,7 @@ docker-test-coverage: $(TMP_DIR_MOUNT_FLAG) \ --mount type=bind,source=`pwd`,destination=/go/src/go.etcd.io/etcd \ gcr.io/etcd-development/etcd-test:go$(GO_VERSION) \ - /bin/bash -c "set -o pipefail; (COVERDIR=covdir PASSES='build build_cov cov' ./test.sh 2>&1 | tee docker-test-coverage-$(TEST_SUFFIX).log) && /codecov -t 6040de41-c073-4d6f-bbf8-d89256ef31e1" + /bin/bash ./scripts/codecov_upload.sh docker-test-coverage-$(TEST_SUFFIX).log \ ! egrep "(--- FAIL:|DATA RACE|panic: test timed out|appears to have leaked)" -B50 -A10 docker-test-coverage-$(TEST_SUFFIX).log diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..aed42c014 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,18 @@ +codecov: + token: "6040de41-c073-4d6f-bbf8-d89256ef31e1" + disable_default_path_fixes: true + +fixes: + - "go.etcd.io/etcd/api/v3/::api/" + - "go.etcd.io/etcd/client/v3/::client/v3/" + - "go.etcd.io/etcd/client/v2/::client/v2/" + - "go.etcd.io/etcd/etcdctl/v3::etcdctl/" + - "go.etcd.io/etcd/pkg/v3::pkg/" + - "go.etcd.io/etcd/raft/v3::raft/" + - "go.etcd.io/etcd/server/v3::server/" + +ignore: + - "**/*.pb.go" + - "**/*.pb.gw.go" + - "tests/**/*" + - "go.etcd.io/etcd/tests/**/*" \ No newline at end of file diff --git a/pkg/testutil/leak.go b/pkg/testutil/leak.go index 4b7a4a9b5..c24adcce8 100644 --- a/pkg/testutil/leak.go +++ b/pkg/testutil/leak.go @@ -124,7 +124,8 @@ func interestingGoroutines() (gs []string) { strings.Contains(stack, "github.com/golang/glog.(*loggingT).flushDaemon") || strings.Contains(stack, "created by runtime.gc") || strings.Contains(stack, "created by text/template/parse.lex") || - strings.Contains(stack, "runtime.MHeap_Scavenger") { + strings.Contains(stack, "runtime.MHeap_Scavenger") || + strings.Contains(stack, "rcrypto/internal/boring.(*PublicKeyRSA).finalize") { continue } gs = append(gs, stack) diff --git a/scripts/codecov_upload.sh b/scripts/codecov_upload.sh new file mode 100755 index 000000000..f75ad60e0 --- /dev/null +++ b/scripts/codecov_upload.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Script used to collect and upload test coverage (mostly by travis). +# Usage ./test_coverage_upload.sh [log_file] + +set -o pipefail + +LOG_FILE=${1:-test-coverage.log} + +# We collect the coverage +COVERDIR=covdir PASSES='build build_cov cov' ./test.sh 2>&1 | tee "${LOG_FILE}" +test_success="$?" + +# We try to upload whatever we have: +bash <(curl -s https://codecov.io/bash) -f ./covdir/all.coverprofile -cF all || exit 2 + +# Expose the original status of the test coverage execution. +exit ${test_success} diff --git a/scripts/test_lib.sh b/scripts/test_lib.sh index d5e422725..984689d62 100644 --- a/scripts/test_lib.sh +++ b/scripts/test_lib.sh @@ -143,7 +143,7 @@ function run { fi log_cmd "% ${repro}" - "${@}" 2> >(while read -r line; do echo -e "stderr: ${COLOR_MAGENTA}${line}${COLOR_NONE}" >&2; done) + "${@}" 2> >(while read -r line; do echo -e "${COLOR_NONE}stderr: ${COLOR_MAGENTA}${line}${COLOR_NONE}">&2; done) local error_code=$? if [ ${error_code} -ne 0 ]; then log_error -e "FAIL: (code:${error_code}):\n % ${repro}" diff --git a/test.sh b/test.sh index be27e6beb..fdde245cf 100755 --- a/test.sh +++ b/test.sh @@ -207,12 +207,77 @@ function pkg_to_coverprofileflag { local prefix="${1}" local pkgs="${2}" local pkgs_normalized - pkgs_normalized=$(echo "${pkgs}" | tr "./ " "__+") - echo -n "-coverprofile=${coverdir}/${prefix}_${pkgs_normalized}.coverprofile" + prefix_normalized=$(echo "${prefix}" | tr "./ " "__+") + if [ "${pkgs}" == "./..." ]; then + pkgs_normalized="all" + else + pkgs_normalized=$(echo "${pkgs}" | tr "./ " "__+") + fi + mkdir -p "${coverdir}/${prefix_normalized}" + echo -n "-coverprofile=${coverdir}/${prefix_normalized}/${pkgs_normalized}.coverprofile" } function not_test_packages { - run_for_modules pkgs_in_module "./..." | grep -v /etcd/tests/v3/ + for m in $(modules); do + if [[ $m =~ .*/etcd/tests/v3 ]]; then continue; fi + if [[ $m =~ .*/etcd/v3 ]]; then continue; fi + echo "${m}/..." + done +} + +# split_dir [dir] [num] +function split_dir { + local d="${1}" + local num="${2}" + local i=0 + for f in "${d}/"*; do + local g=$(( "${i}" % "${num}" )) + mkdir -p "${d}_${g}" + mv "${f}" "${d}_${g}/" + (( i++ )) + done +} + +function split_dir_pass { + split_dir ./covdir/integration 4 +} + + +# merge_cov_files [coverdir] [outfile] +# merges all coverprofile files into a single file in the given directory. +function merge_cov_files { + local coverdir="${1}" + local cover_out_file="${2}" + log_callout "Merging coverage results in: ${coverdir}" + # gocovmerge requires not-empty test to start with: + echo "mode: set" > "${cover_out_file}" + + local i=0 + local count + count=$(find "${coverdir}"/*.coverprofile | wc -l) + for f in "${coverdir}"/*.coverprofile; do + # print once per 20 files + if ! (( "${i}" % 20 )); then + log_callout "${i} of ${count}: Merging file: ${f}" + fi + run_go_tool "github.com/gyuho/gocovmerge" "${f}" "${cover_out_file}" > "${coverdir}/cover.tmp" 2>/dev/null + if [ -s "${coverdir}"/cover.tmp ]; then + mv "${coverdir}/cover.tmp" "${cover_out_file}" + fi + (( i++ )) + done +} + +# merge_cov [coverdir] +function merge_cov { + log_callout "[$(date)] Merging coverage files ..." + coverdir="${1}" + for d in "${coverdir}"/*/; do + d=${d%*/} # remove the trailing "/" + merge_cov_files "${d}" "${d}.coverprofile" & + done + wait + merge_cov_files "${coverdir}" "${coverdir}/all.coverprofile" } function cov_pass { @@ -230,7 +295,7 @@ function cov_pass { local coverdir coverdir=$(readlink -f "${COVERDIR}") mkdir -p "${coverdir}" - rm -f "${coverdir}/*.coverprofile" "${coverdir}/cover.*" + find "${coverdir}" -print0 -name '*.coverprofile' | xargs -0 rm local covpkgs covpkgs=$(not_test_packages) @@ -240,45 +305,47 @@ function cov_pass { local failed="" - log_callout "Collecting coverage from unit tests ..." + log_callout "[$(date)] Collecting coverage from unit tests ..." for m in $(module_dirs); do - run_for_module "${m}" go_test "./..." "keep_going" "pkg_to_coverprofileflag unit" -short -timeout=30m \ + run_for_module "${m}" go_test "./..." "parallel" "pkg_to_coverprofileflag unit_${m}" -short -timeout=30m \ "${gocov_build_flags[@]}" "$@" || failed="$failed unit" done - log_callout "Collecting coverage from integration tests ..." - run_for_module "tests" go_test "./integration/..." "keep_going" "pkg_to_coverprofileflag integration" \ + log_callout "[$(date)] Collecting coverage from integration tests ..." + run_for_module "tests" go_test "./integration/..." "parallel" "pkg_to_coverprofileflag integration" \ -timeout=30m "${gocov_build_flags[@]}" "$@" || failed="$failed integration" # integration-store-v2 run_for_module "tests" go_test "./integration/v2store/..." "keep_going" "pkg_to_coverprofileflag store_v2" \ -tags v2v3 -timeout=5m "${gocov_build_flags[@]}" "$@" || failed="$failed integration_v2v3" # integration_cluster_proxy - run_for_module "tests" go_test "./integration/..." "keep_going" "pkg_to_coverprofileflag integration_cluster_proxy" \ + run_for_module "tests" go_test "./integration/..." "parallel" "pkg_to_coverprofileflag integration_cluster_proxy" \ -tags cluster_proxy -timeout=5m "${gocov_build_flags[@]}" || failed="$failed integration_cluster_proxy" - log_callout "Collecting coverage from e2e tests ..." + log_callout "[$(date)] Collecting coverage from e2e tests ..." # We don't pass 'gocov_build_flags' nor 'pkg_to_coverprofileflag' here, - # as the coverage is colleced from the ./bin/etcd_test & ./bin/etcdctl_test internally spawned. - run_for_module "tests" go_test "./e2e/..." "keep_going" : -tags=cov -timeout 30m "$@" || failed="$failed tests_e2e" + # as the coverage is collected from the ./bin/etcd_test & ./bin/etcdctl_test internally spawned. + mkdir -p "${COVERDIR}/e2e" + COVERDIR="${COVERDIR}/e2e" run_for_module "tests" go_test "./e2e/..." "keep_going" : -tags=cov -timeout 30m "$@" || failed="$failed tests_e2e" + split_dir "${COVERDIR}/e2e" 10 - log_callout "Collecting coverage from e2e tests with proxy ..." - run_for_module "tests" go_test "./e2e/..." "keep_going" : -tags="cov cluster_proxy" -timeout 30m "$@" || failed="$failed tests_e2e_proxy" + log_callout "[$(date)] Collecting coverage from e2e tests with proxy ..." + mkdir -p "${COVERDIR}/e2e_proxy" + COVERDIR="${COVERDIR}/e2e_proxy" run_for_module "tests" go_test "./e2e/..." "keep_going" : -tags="cov cluster_proxy" -timeout 30m "$@" || failed="$failed tests_e2e_proxy" + split_dir "${COVERDIR}/e2e_proxy" 10 - log_callout "Merging coverage results ..." - local cover_out_file="${coverdir}/cover.out" - # gocovmerge requires not-empty test to start with: - echo "mode: set" > "${cover_out_file}" + local cover_out_file="${coverdir}/all.coverprofile" + merge_cov "${coverdir}" - # incrementally merge to get coverage data even if some coverage files are corrupted - for f in "${coverdir}"/*.coverprofile; do - echo "merging test coverage file ${f}" - run_go_tool "github.com/gyuho/gocovmerge" "${f}" "${cover_out_file}" > "${coverdir}/cover.tmp" || failed="$failed gocovmerge:$f" - if [ -s "${coverdir}"/cover.tmp ]; then - mv "${coverdir}/cover.tmp" "${cover_out_file}" - fi - done # strip out generated files (using GNU-style sed) - sed --in-place '/generated.go/d' "${cover_out_file}" || true + sed --in-place -E "/[.]pb[.](gw[.])?go/d" "${cover_out_file}" || true + + sed --in-place -E "s|go.etcd.io/etcd/api/v3/|api/|g" "${cover_out_file}" || true + sed --in-place -E "s|go.etcd.io/etcd/client/v3/|client/v3/|g" "${cover_out_file}" || true + sed --in-place -E "s|go.etcd.io/etcd/client/v2/|client/v2/|g" "${cover_out_file}" || true + sed --in-place -E "s|go.etcd.io/etcd/etcdctl/v3/|etcdctl/|g" "${cover_out_file}" || true + sed --in-place -E "s|go.etcd.io/etcd/pkg/v3/|pkg/|g" "${cover_out_file}" || true + sed --in-place -E "s|go.etcd.io/etcd/raft/v3/|raft/|g" "${cover_out_file}" || true + sed --in-place -E "s|go.etcd.io/etcd/server/v3/|server/|g" "${cover_out_file}" || true # held failures to generate the full coverage file, now fail if [ -n "$failed" ]; then @@ -626,6 +693,7 @@ function run_pass { fi } +log_callout "Starting at: $(date)" for pass in $PASSES; do run_pass "${pass}" "${@}" done