Skip to content

Commit a7da865

Browse files
committed
test: filter "go test" output with gotestsum instead of grep
Filtering the output with grep leads to hard to read log output, e.g. from pull-kubernetes-unit: +++ [0613 15:32:48] Running tests without code coverage and with -race {"Time":"2024-06-13T15:33:47.845457374Z","Action":"output","Package":"k8s.io/kubernetes/cluster/gce/cos","Test":"TestCreateMasterAuditPolicy","Output":" /tmp/configure-helper-test47992121/kube-env: line 1: `}'\n"} {"Time":"2024-06-13T15:33:49.053732803Z","Action":"output","Package":"k8s.io/kubernetes/cluster/gce/cos","Output":"ok \tk8s.io/kubernetes/cluster/gce/cos\t2.906s\n"} We can do better than that. When feeding the output of the "go test" command(s) into gotestsum *while it runs*, we can use --format=standard-quiet (= normal go test output) or --format=standard-verbose (= `go test -v`) when FULL_LOG is requested to get nicer output. This works when testing everything at once. This was said to be not possible when doing coverage profiling. But recent Go no longer has that limitation, so the xargs trick gets removed. All that we need to do for coverage profiling is to add some additional parameters and the conversion to HTML.
1 parent cb7b4ea commit a7da865

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)