Skip to content

Commit 2049360

Browse files
authored
Merge pull request kubernetes#125534 from pohly/gotestsum
filter `go test` output live with gotestsum
2 parents 0e4cf67 + a7da865 commit 2049360

File tree

3 files changed

+108
-127
lines changed

3 files changed

+108
-127
lines changed

hack/make-rules/test.sh

Lines changed: 59 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,6 @@ KUBE_COVERMODE=${KUBE_COVERMODE:-atomic}
6666
# The directory to save test coverage reports to, if generating them. If unset,
6767
# a semi-predictable temporary directory will be used.
6868
KUBE_COVER_REPORT_DIR="${KUBE_COVER_REPORT_DIR:-}"
69-
# How many 'go test' instances to run simultaneously when running tests in
70-
# coverage mode.
71-
KUBE_COVERPROCS=${KUBE_COVERPROCS:-4}
7269
# use KUBE_RACE="" to disable the race detector
7370
# this is defaulted to "-race" in make test as well
7471
# NOTE: DO NOT ADD A COLON HERE. KUBE_RACE="" is meaningful!
@@ -133,26 +130,21 @@ done
133130
shift $((OPTIND - 1))
134131

135132
# Use eval to preserve embedded quoted strings.
133+
#
134+
# KUBE_TEST_ARGS contains arguments for `go test` (like -short)
135+
# and may end with `-args <arguments for test binary>`, so it
136+
# has to be passed to `go test` at the end of the invocation.
136137
testargs=()
137138
eval "testargs=(${KUBE_TEST_ARGS:-})"
138139

139-
# Used to filter verbose test output.
140-
go_test_grep_pattern=".*"
141-
142-
goflags=()
143-
# The junit report tool needs full test case information to produce a
144-
# meaningful report.
145-
if [[ -n "${KUBE_JUNIT_REPORT_DIR}" ]] ; then
146-
goflags+=(-v)
147-
goflags+=(-json)
148-
# Show only summary lines by matching lines like "status package/test"
149-
go_test_grep_pattern="^[^[:space:]]\+[[:space:]]\+[^[:space:]]\+/[^[[:space:]]\+"
150-
fi
151-
140+
# gotestsum --format value
141+
gotestsum_format=standard-quiet
152142
if [[ -n "${FULL_LOG:-}" ]] ; then
153-
go_test_grep_pattern=".*"
143+
gotestsum_format=standard-verbose
154144
fi
155145

146+
goflags=()
147+
156148
# Filter out arguments that start with "-" and move them to goflags.
157149
testcases=()
158150
for arg; do
@@ -180,113 +172,78 @@ junitFilenamePrefix() {
180172
echo "${KUBE_JUNIT_REPORT_DIR}/junit_$(kube::util::sortable_date)"
181173
}
182174

183-
produceJUnitXMLReport() {
184-
local -r junit_filename_prefix=$1
185-
if [[ -z "${junit_filename_prefix}" ]]; then
186-
return
187-
fi
188-
189-
local junit_xml_filename
190-
junit_xml_filename="${junit_filename_prefix}.xml"
191-
175+
installTools() {
192176
if ! command -v gotestsum >/dev/null 2>&1; then
193177
kube::log::status "gotestsum not found; installing from ./hack/tools"
194178
go -C "${KUBE_ROOT}/hack/tools" install gotest.tools/gotestsum
195179
fi
196-
gotestsum --junitfile "${junit_xml_filename}" --raw-command cat "${junit_filename_prefix}"*.stdout
197-
if [[ ! ${KUBE_KEEP_VERBOSE_TEST_OUTPUT} =~ ^[yY]$ ]]; then
198-
rm "${junit_filename_prefix}"*.stdout
199-
fi
200180

201181
if ! command -v prune-junit-xml >/dev/null 2>&1; then
202182
kube::log::status "prune-junit-xml not found; installing from ./cmd"
203183
go -C "${KUBE_ROOT}/cmd/prune-junit-xml" install .
204184
fi
205-
prune-junit-xml "${junit_xml_filename}"
206-
207-
kube::log::status "Saved JUnit XML test report to ${junit_xml_filename}"
208185
}
209186

210187
runTests() {
211188
local junit_filename_prefix
212189
junit_filename_prefix=$(junitFilenamePrefix)
213190

214-
# Try to normalize input names.
191+
installTools
192+
193+
# Try to normalize input names. This is slow!
215194
local -a targets
195+
kube::log::status "Normalizing Go targets"
216196
kube::util::read-array targets < <(kube::golang::normalize_go_targets "$@")
217197

218-
# If we're not collecting coverage, run all requested tests with one 'go test'
219-
# command, which is much faster.
220-
if [[ ! ${KUBE_COVER} =~ ^[yY]$ ]]; then
221-
kube::log::status "Running tests without code coverage ${KUBE_RACE:+"and with ${KUBE_RACE}"}"
222-
# shellcheck disable=SC2031
223-
go test "${goflags[@]:+${goflags[@]}}" \
224-
"${KUBE_TIMEOUT}" "${targets[@]}" \
225-
"${testargs[@]:+${testargs[@]}}" \
226-
| tee ${junit_filename_prefix:+"${junit_filename_prefix}.stdout"} \
227-
| grep --binary-files=text "${go_test_grep_pattern}" && rc=$? || rc=$?
228-
produceJUnitXMLReport "${junit_filename_prefix}"
229-
return "${rc}"
198+
# Enable coverage data collection?
199+
local cover_msg
200+
local COMBINED_COVER_PROFILE
201+
202+
if [[ ${KUBE_COVER} =~ ^[yY]$ ]]; then
203+
cover_msg="with code coverage"
204+
if [[ -z "${KUBE_COVER_REPORT_DIR}" ]]; then
205+
cover_report_dir="/tmp/k8s_coverage/$(kube::util::sortable_date)"
206+
else
207+
cover_report_dir="${KUBE_COVER_REPORT_DIR}"
208+
fi
209+
kube::log::status "Saving coverage output in '${cover_report_dir}'"
210+
mkdir -p "${@+${@/#/${cover_report_dir}/}}"
211+
COMBINED_COVER_PROFILE="${cover_report_dir}/combined-coverage.out"
212+
goflags+=(-cover -covermode="${KUBE_COVERMODE}" -coverprofile="${COMBINED_COVER_PROFILE}")
213+
else
214+
cover_msg="without code coverage"
215+
fi
216+
217+
# Keep the raw JSON output in addition to the JUnit file?
218+
local jsonfile=""
219+
if [[ -n "${junit_filename_prefix}" ]] && [[ ${KUBE_KEEP_VERBOSE_TEST_OUTPUT} =~ ^[yY]$ ]]; then
220+
jsonfile="${junit_filename_prefix}.stdout"
230221
fi
231222

232-
kube::log::status "Running tests with code coverage ${KUBE_RACE:+"and with ${KUBE_RACE}"}"
223+
kube::log::status "Running tests ${cover_msg} ${KUBE_RACE:+"and with ${KUBE_RACE}"}"
224+
gotestsum --format="${gotestsum_format}" \
225+
--jsonfile="${jsonfile}" \
226+
--junitfile="${junit_filename_prefix:+"${junit_filename_prefix}.xml"}" \
227+
--raw-command \
228+
-- \
229+
go test -json \
230+
"${goflags[@]:+${goflags[@]}}" \
231+
"${KUBE_TIMEOUT}" \
232+
"${targets[@]}" \
233+
"${testargs[@]:+${testargs[@]}}" \
234+
&& rc=$? || rc=$?
235+
236+
if [[ -n "${junit_filename_prefix}" ]]; then
237+
prune-junit-xml "${junit_filename_prefix}.xml"
238+
fi
233239

234-
# Create coverage report directories.
235-
if [[ -z "${KUBE_COVER_REPORT_DIR}" ]]; then
236-
cover_report_dir="/tmp/k8s_coverage/$(kube::util::sortable_date)"
237-
else
238-
cover_report_dir="${KUBE_COVER_REPORT_DIR}"
240+
if [[ ${KUBE_COVER} =~ ^[yY]$ ]]; then
241+
coverage_html_file="${cover_report_dir}/combined-coverage.html"
242+
go tool cover -html="${COMBINED_COVER_PROFILE}" -o="${coverage_html_file}"
243+
kube::log::status "Combined coverage report: ${coverage_html_file}"
239244
fi
240-
cover_profile="coverage.out" # Name for each individual coverage profile
241-
kube::log::status "Saving coverage output in '${cover_report_dir}'"
242-
mkdir -p "${@+${@/#/${cover_report_dir}/}}"
243-
244-
# Run all specified tests, collecting coverage results. Go currently doesn't
245-
# support collecting coverage across multiple packages at once, so we must issue
246-
# separate 'go test' commands for each package and then combine at the end.
247-
# To speed things up considerably, we can at least use xargs -P to run multiple
248-
# 'go test' commands at once.
249-
# To properly parse the test results if generating a JUnit test report, we
250-
# must make sure the output from PARALLEL runs is not mixed. To achieve this,
251-
# we spawn a subshell for each PARALLEL process, redirecting the output to
252-
# separate files.
253-
254-
printf "%s\n" "${@}" \
255-
| xargs -I{} -n 1 -P "${KUBE_COVERPROCS}" \
256-
bash -c "set -o pipefail; _pkg=\"\$0\"; _pkg_out=\${_pkg//\//_}; \
257-
go test ${goflags[*]:+${goflags[*]}} \
258-
${KUBE_TIMEOUT} \
259-
-cover -covermode=\"${KUBE_COVERMODE}\" \
260-
-coverprofile=\"${cover_report_dir}/\${_pkg}/${cover_profile}\" \
261-
\"\${_pkg}\" \
262-
${testargs[*]:+${testargs[*]}} \
263-
| tee ${junit_filename_prefix:+\"${junit_filename_prefix}-\$_pkg_out.stdout\"} \
264-
| grep \"${go_test_grep_pattern}\"" \
265-
{} \
266-
&& test_result=$? || test_result=$?
267-
268-
produceJUnitXMLReport "${junit_filename_prefix}"
269-
270-
COMBINED_COVER_PROFILE="${cover_report_dir}/combined-coverage.out"
271-
{
272-
# The combined coverage profile needs to start with a line indicating which
273-
# coverage mode was used (set, count, or atomic). This line is included in
274-
# each of the coverage profiles generated when running 'go test -cover', but
275-
# we strip these lines out when combining so that there's only one.
276-
echo "mode: ${KUBE_COVERMODE}"
277-
278-
# Include all coverage reach data in the combined profile, but exclude the
279-
# 'mode' lines, as there should be only one.
280-
while IFS='' read -r x; do
281-
grep -h -v "^mode:" < "${x}" || true
282-
done < <(find "${cover_report_dir}" -name "${cover_profile}")
283-
} >"${COMBINED_COVER_PROFILE}"
284-
285-
coverage_html_file="${cover_report_dir}/combined-coverage.html"
286-
go tool cover -html="${COMBINED_COVER_PROFILE}" -o="${coverage_html_file}"
287-
kube::log::status "Combined coverage report: ${coverage_html_file}"
288-
289-
return "${test_result}"
245+
246+
return "${rc}"
290247
}
291248

292249
reportCoverageToCoveralls() {

hack/tools/go.mod

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ require (
1010
github.com/jcchavezs/porto v0.6.0
1111
github.com/vektra/mockery/v2 v2.40.3
1212
go.uber.org/automaxprocs v1.5.2
13-
gotest.tools/gotestsum v1.6.4
13+
gotest.tools/gotestsum v1.12.0
1414
honnef.co/go/tools v0.5.1
1515
sigs.k8s.io/logtools v0.8.1
1616
)
@@ -35,6 +35,7 @@ require (
3535
github.com/ashanbrown/forbidigo v1.6.0 // indirect
3636
github.com/ashanbrown/makezero v1.1.1 // indirect
3737
github.com/beorn7/perks v1.0.1 // indirect
38+
github.com/bitfield/gotestdox v0.2.2 // indirect
3839
github.com/bkielbasa/cyclop v1.2.1 // indirect
3940
github.com/blizzy78/varnamelen v0.8.0 // indirect
4041
github.com/bombsimon/wsl/v4 v4.2.1 // indirect
@@ -58,7 +59,7 @@ require (
5859
github.com/fatih/color v1.16.0 // indirect
5960
github.com/fatih/structtag v1.2.0 // indirect
6061
github.com/firefart/nonamedreturns v1.0.4 // indirect
61-
github.com/fsnotify/fsnotify v1.6.0 // indirect
62+
github.com/fsnotify/fsnotify v1.7.0 // indirect
6263
github.com/fzipp/gocyclo v0.6.0 // indirect
6364
github.com/ghostiam/protogetter v0.3.4 // indirect
6465
github.com/go-critic/go-critic v0.11.1 // indirect
@@ -101,7 +102,6 @@ require (
101102
github.com/jinzhu/copier v0.3.5 // indirect
102103
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
103104
github.com/jjti/go-spancheck v0.5.2 // indirect
104-
github.com/jonboulle/clockwork v0.2.2 // indirect
105105
github.com/julz/importas v0.1.0 // indirect
106106
github.com/kisielk/errcheck v1.7.0 // indirect
107107
github.com/kisielk/gotool v1.0.0 // indirect
@@ -133,7 +133,6 @@ require (
133133
github.com/nunnatsa/ginkgolinter v0.15.2 // indirect
134134
github.com/olekukonko/tablewriter v0.0.5 // indirect
135135
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
136-
github.com/pkg/errors v0.9.1 // indirect
137136
github.com/pmezard/go-difflib v1.0.0 // indirect
138137
github.com/polyfloyd/go-errorlint v1.4.8 // indirect
139138
github.com/prometheus/client_golang v1.12.1 // indirect
@@ -189,13 +188,12 @@ require (
189188
go.uber.org/atomic v1.9.0 // indirect
190189
go.uber.org/multierr v1.8.0 // indirect
191190
go.uber.org/zap v1.24.0 // indirect
192-
golang.org/x/crypto v0.19.0 // indirect
193191
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
194192
golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 // indirect
195193
golang.org/x/mod v0.17.0 // indirect
196194
golang.org/x/sync v0.7.0 // indirect
197195
golang.org/x/sys v0.20.0 // indirect
198-
golang.org/x/term v0.17.0 // indirect
196+
golang.org/x/term v0.18.0 // indirect
199197
golang.org/x/text v0.14.0 // indirect
200198
golang.org/x/tools v0.21.1-0.20240531212143-b6235391adb3 // indirect
201199
google.golang.org/protobuf v1.34.2 // indirect

0 commit comments

Comments
 (0)