1#!/usr/bin/env bash
2#
3# Run all etcd tests
4# ./test
5# ./test -v
6#
7#
8# Run specified test pass
9#
10# $ PASSES=unit ./test
11# $ PASSES=integration ./test
12#
13#
14# Run tests for one package
15# Each pass has different default timeout, if you just run tests in one package or 1 test case then you can set TIMEOUT
16# flag for different expectation
17#
18# $ PASSES=unit PKG=./wal TIMEOUT=1m ./test
19# $ PASSES=integration PKG=client/integration TIMEOUT=1m ./test
20#
21#
22# Run specified unit tests in one package
23# To run all the tests with prefix of "TestNew", set "TESTCASE=TestNew ";
24# to run only "TestNew", set "TESTCASE="\bTestNew\b""
25#
26# $ PASSES=unit PKG=./wal TESTCASE=TestNew TIMEOUT=1m ./test
27# $ PASSES=unit PKG=./wal TESTCASE="\bTestNew\b" TIMEOUT=1m ./test
28# $ PASSES=integration PKG=client/integration TESTCASE="\bTestV2NoRetryEOF\b" TIMEOUT=1m ./test
29#
30#
31# Run code coverage
32# COVERDIR must either be a absolute path or a relative path to the etcd root
33# $ COVERDIR=coverage PASSES="build_cov cov" ./test
34set -e
35
36source ./build
37
38# build before setting up test GOPATH
39if [[ "${PASSES}" == *"functional"* ]]; then
40	./functional/build
41fi
42
43if [ -z "$PASSES" ]; then
44	PASSES="fmt bom dep build unit"
45fi
46
47USERPKG=${PKG:-}
48
49# Invoke ./tests/cover.test.bash for HTML output
50COVER=${COVER:-"-cover"}
51
52# Hack: gofmt ./ will recursively check the .git directory. So use *.go for gofmt.
53IGNORE_PKGS="(vendor/|etcdserverpb|rafttest|gopath.proto|v3lockpb|v3electionpb)"
54INTEGRATION_PKGS="(integration|tests/e2e|contrib|functional)"
55
56# all github.com/etcd-io/etcd/whatever pkgs that are not auto-generated / tools
57# shellcheck disable=SC1117
58PKGS=$(find . -name \*.go | while read -r a; do dirname "$a"; done | sort | uniq | grep -vE "$IGNORE_PKGS" | grep -vE "(tools/|contrib/|tests/e2e|pb)" | sed "s|\.|${REPO_PATH}|g" | xargs echo)
59# pkg1,pkg2,pkg3
60PKGS_COMMA=${PKGS// /,}
61
62# shellcheck disable=SC1117
63TEST_PKGS=$(find . -name \*_test.go | while read -r a; do dirname "$a"; done | sort | uniq | grep -vE "$IGNORE_PKGS" | sed "s|\./||g")
64
65# shellcheck disable=SC1117
66FORMATTABLE=$(find . -name \*.go | while read -r a; do echo "$(dirname "$a")/*.go"; done | sort | uniq | grep -vE "$IGNORE_PKGS" | sed "s|\./||g")
67
68TESTABLE_AND_FORMATTABLE=$(echo "$TEST_PKGS" | grep -vE "$INTEGRATION_PKGS")
69
70# check if user provided PKG override
71if [ -z "${USERPKG}" ]; then
72	TEST=$TESTABLE_AND_FORMATTABLE
73	FMT=$FORMATTABLE
74else
75	# strip out leading dotslashes and trailing slashes from PKG=./foo/
76	TEST=${USERPKG/#./}
77	TEST=${TEST/#\//}
78	TEST=${TEST/%\//}
79	# only run gofmt on packages provided by user
80	FMT="$TEST"
81fi
82
83# shellcheck disable=SC2206
84FMT=($FMT)
85if [ "${VERBOSE}" == "1" ]; then
86	# shellcheck disable=SC2128
87	echo "Running with FMT:" "${FMT[@]}"
88fi
89
90# prepend REPO_PATH to each local package
91split=$TEST
92TEST=""
93for a in $split; do TEST="$TEST ${REPO_PATH}/${a}"; done
94
95# shellcheck disable=SC2206
96TEST=($TEST)
97if [ "${VERBOSE}" == "1" ]; then
98	# shellcheck disable=SC2128
99	echo "Running with TEST:" "${TEST[@]}"
100fi
101
102# TODO: 'rafttest' is failing with unused
103STATIC_ANALYSIS_PATHS=$(find . -name \*.go ! -path './vendor/*' ! -path './gopath.proto/*' ! -path '*pb/*' | while read -r a; do dirname "$a"; done | sort | uniq | grep -vE "$IGNORE_PKGS")
104# shellcheck disable=SC2206
105STATIC_ANALYSIS_PATHS=($STATIC_ANALYSIS_PATHS)
106if [ "${VERBOSE}" == "1" ]; then
107	# shellcheck disable=SC2128
108	echo "Running with STATIC_ANALYSIS_PATHS:" "${STATIC_ANALYSIS_PATHS[@]}"
109fi
110
111if [ -z "$GOARCH" ]; then
112	GOARCH=$(go env GOARCH);
113fi
114
115# determine the number of CPUs to use for Go tests
116TEST_CPUS="1,2,4"
117if [ -n "${CPU}" ]; then
118	TEST_CPUS="${CPU}"
119fi
120echo "Running with TEST_CPUS:" "${TEST_CPUS}"
121
122# determine whether target supports race detection
123if [ -z "${RACE}" ] ; then
124  if [ "$GOARCH" == "amd64" ]; then
125    RACE="--race"
126  else
127    RACE="--race=false"
128  fi
129else
130  RACE="--race=${RACE:-true}"
131fi
132
133RUN_ARG=""
134if [ -n "${TESTCASE}" ]; then
135	RUN_ARG="-run=${TESTCASE}"
136fi
137
138function unit_pass {
139	echo "Running unit tests..."
140	GO_TEST_FLAG=""
141	if [ "${VERBOSE}" == "1" ]; then
142		GO_TEST_FLAG="-v"
143	fi
144	if [ "${VERBOSE}" == "2" ]; then
145		GO_TEST_FLAG="-v"
146		export CLIENT_DEBUG=1
147	fi
148
149	if [ "${RUN_ARG}" == "" ]; then
150	    RUN_ARG="-run=Test"
151	fi
152
153	# check if user provided time out, especially useful when just run one test case
154	# expectation could be different
155	USERTIMEOUT=""
156	if [ -z "${TIMEOUT}" ]; then
157		USERTIMEOUT="3m"
158	else
159		USERTIMEOUT="${TIMEOUT}"
160	fi
161	go test ${GO_TEST_FLAG} -timeout "${USERTIMEOUT}"  "${COVER}" ${RACE} -cpu "${TEST_CPUS}" ${RUN_ARG} "$@" "${TEST[@]}"
162}
163
164function integration_pass {
165	echo "Running integration tests..."
166
167	# check if user provided time out, especially useful when just run one test case
168	# expectation could be different
169	USERTIMEOUT=""
170	if [ -z "${TIMEOUT}" ]; then
171		USERTIMEOUT="30m"
172	else
173		USERTIMEOUT="${TIMEOUT}"
174	fi
175
176	# if TESTCASE and PKG set, run specified test case in specified PKG
177	# if TESTCASE set, PKG not set, run specified test case in all integration and integration_extra packages
178	# if TESTCASE not set, PKG set, run all test cases in specified package
179	# if TESTCASE not set, PKG not set, run all tests in all integration and integration_extra packages
180	if [ -z "${TESTCASE}" ] && [ -z "${USERPKG}" ]; then
181		go test -timeout "${USERTIMEOUT}" -v -cpu "${TEST_CPUS}" "$@" "${REPO_PATH}/integration"
182		integration_extra "$@"
183	else
184		if [ -z "${USERPKG}" ]; then
185			INTEGTESTPKG=("${REPO_PATH}/integration"
186						  "${REPO_PATH}/client/integration"
187						  "${REPO_PATH}/clientv3/integration"
188						  "${REPO_PATH}/contrib/raftexample"
189						  "${REPO_PATH}/store")
190		else
191			INTEGTESTPKG=("${TEST[@]}")
192		fi
193		go test -timeout "${USERTIMEOUT}" -v -cpu "${TEST_CPUS}" "${RUN_ARG}"  "$@" "${INTEGTESTPKG[@]}"
194	fi
195}
196
197function integration_extra {
198	go test -timeout 1m -v ${RACE} -cpu "${TEST_CPUS}" "$@" "${REPO_PATH}/client/integration"
199	go test -timeout 25m -v ${RACE} -cpu "${TEST_CPUS}" "$@" "${REPO_PATH}/clientv3/integration"
200	go test -timeout 1m -v -cpu "${TEST_CPUS}" "$@" "${REPO_PATH}/contrib/raftexample"
201	go test -timeout 5m -v ${RACE} -tags v2v3 "$@" "${REPO_PATH}/etcdserver/api/v2store"
202	go test -timeout 1m -v ${RACE} -cpu "${TEST_CPUS}" -run=Example "$@" "${TEST[@]}"
203}
204
205function functional_pass {
206  	# Clean up any data and logs from previous runs
207  	rm -rf /tmp/etcd-functional-* /tmp/etcd-functional-*.backup
208
209	for a in 1 2 3; do
210		./bin/etcd-agent --network tcp --address 127.0.0.1:${a}9027 &
211		pid="$!"
212		agent_pids="${agent_pids} $pid"
213	done
214
215	for a in 1 2 3; do
216		echo "Waiting for 'etcd-agent' on ${a}9027..."
217		while ! nc -z localhost ${a}9027; do
218			sleep 1
219		done
220	done
221
222	echo "functional test START!"
223	./bin/etcd-tester --config ./functional.yaml && echo "'etcd-tester' succeeded"
224	ETCD_TESTER_EXIT_CODE=$?
225	echo "ETCD_TESTER_EXIT_CODE:" ${ETCD_TESTER_EXIT_CODE}
226
227	# shellcheck disable=SC2206
228	agent_pids=($agent_pids)
229	kill -s TERM "${agent_pids[@]}" || true
230
231	if [[ "${ETCD_TESTER_EXIT_CODE}" -ne "0" ]]; then
232		printf "\n"
233		echo "FAILED! 'tail -1000 /tmp/etcd-functional-1/etcd.log'"
234		tail -1000 /tmp/etcd-functional-1/etcd.log
235
236		printf "\n"
237		echo "FAILED! 'tail -1000 /tmp/etcd-functional-2/etcd.log'"
238		tail -1000 /tmp/etcd-functional-2/etcd.log
239
240		printf "\n"
241		echo "FAILED! 'tail -1000 /tmp/etcd-functional-3/etcd.log'"
242		tail -1000 /tmp/etcd-functional-3/etcd.log
243
244		echo "--- FAIL: exit code" ${ETCD_TESTER_EXIT_CODE}
245		exit ${ETCD_TESTER_EXIT_CODE}
246	fi
247	echo "functional test PASS!"
248}
249
250function cov_pass {
251	echo "Running code coverage..."
252	# install gocovmerge before running code coverage from github.com/wadey/gocovmerge
253	# gocovmerge merges coverage files
254	if ! command -v gocovmerge >/dev/null; then
255		echo "gocovmerge not installed"
256		exit 255
257	fi
258
259	if [ -z "$COVERDIR" ]; then
260		echo "COVERDIR undeclared"
261		exit 255
262	fi
263
264	if [ ! -f "bin/etcd_test" ]; then
265		echo "etcd_test binary not found"
266		exit 255
267	fi
268
269	mkdir -p "$COVERDIR"
270
271	# run code coverage for unit and integration tests
272	GOCOVFLAGS="-covermode=set -coverpkg ${PKGS_COMMA} -v -timeout 30m"
273	# shellcheck disable=SC2206
274	GOCOVFLAGS=($GOCOVFLAGS)
275	failed=""
276	for t in $(echo "${TEST_PKGS}" | grep -vE "(tests/e2e|functional)"); do
277		tf=$(echo "$t" | tr / _)
278		# cache package compilation data for faster repeated builds
279		go test "${GOCOVFLAGS[@]}" -i "${REPO_PATH}/$t" || true
280		# uses -run=Test to skip examples because clientv3/ example tests will leak goroutines
281		go test "${GOCOVFLAGS[@]}" -run=Test -coverprofile "$COVERDIR/${tf}.coverprofile"  "${REPO_PATH}/$t" || failed="$failed $t"
282	done
283
284	# v2v3 tests
285	go test -tags v2v3 "${GOCOVFLAGS[@]}" -coverprofile "$COVERDIR/store-v2v3.coverprofile" "${REPO_PATH}/clientv3/integration" || failed="$failed store-v2v3"
286
287	# proxy tests
288	go test -tags cluster_proxy "${GOCOVFLAGS[@]}" -coverprofile "$COVERDIR/proxy_integration.coverprofile" "${REPO_PATH}/integration" || failed="$failed proxy-integration"
289	go test -tags cluster_proxy "${GOCOVFLAGS[@]}" -coverprofile "$COVERDIR/proxy_clientv3.coverprofile" "${REPO_PATH}/clientv3/integration" || failed="$failed proxy-clientv3/integration"
290
291	# run code coverage for e2e tests
292	# use 30m timeout because e2e coverage takes longer
293	# due to many tests cause etcd process to wait
294	# on leadership transfer timeout during gracefully shutdown
295	echo Testing tests/e2e without proxy...
296	go test -tags cov -timeout 30m -v "${REPO_PATH}/tests/e2e" || failed="$failed tests/e2e"
297	echo Testing tests/e2e with proxy...
298	go test -tags "cov cluster_proxy" -timeout 30m -v "${REPO_PATH}/tests/e2e" || failed="$failed tests/e2e-proxy"
299
300	# incrementally merge to get coverage data even if some coverage files are corrupted
301	# optimistically assume etcdserver package's coverage file is OK since gocovmerge
302	# expects to start with a non-empty file
303	cp "$COVERDIR"/etcdserver.coverprofile "$COVERDIR"/cover.out
304	for f in "$COVERDIR"/*.coverprofile; do
305		echo "merging test coverage file ${f}"
306		gocovmerge "$f" "$COVERDIR"/cover.out  >"$COVERDIR"/cover.tmp || failed="$failed $f"
307		if [ -s "$COVERDIR"/cover.tmp ]; then
308			mv "$COVERDIR"/cover.tmp "$COVERDIR"/cover.out
309		fi
310	done
311	# strip out generated files (using GNU-style sed)
312	sed --in-place '/generated.go/d' "$COVERDIR"/cover.out || true
313
314	# held failures to generate the full coverage file, now fail
315	if [ -n "$failed" ]; then
316		for f in $failed; do
317			echo "--- FAIL:" "$f"
318		done
319		exit 255
320	fi
321}
322
323function e2e_pass {
324	echo "Running e2e tests..."
325
326	# check if user provided time out, especially useful when just run one test case
327	# expectation could be different
328	USERTIMEOUT=""
329	if [ -z "${TIMEOUT}" ]; then
330		USERTIMEOUT="30m"
331	else
332		USERTIMEOUT="${TIMEOUT}"
333	fi
334
335	go test -timeout "${USERTIMEOUT}" -v -cpu "${TEST_CPUS}" "${RUN_ARG}"  "$@" "${REPO_PATH}/tests/e2e"
336}
337
338function integration_e2e_pass {
339	echo "Running integration and e2e tests..."
340
341	go test -timeout 30m -v -cpu "${TEST_CPUS}" "$@" "${REPO_PATH}/tests/e2e" &
342	e2epid="$!"
343	go test -timeout 30m -v -cpu "${TEST_CPUS}" "$@" "${REPO_PATH}/integration" &
344	intpid="$!"
345	wait $e2epid
346	wait $intpid
347	integration_extra "$@"
348}
349
350function grpcproxy_pass {
351	go test -timeout 30m -v ${RACE} -tags cluster_proxy -cpu "${TEST_CPUS}" "$@" "${REPO_PATH}/integration"
352	go test -timeout 30m -v ${RACE} -tags cluster_proxy -cpu "${TEST_CPUS}" "$@" "${REPO_PATH}/clientv3/integration"
353	go test -timeout 30m -v -tags cluster_proxy "$@" "${REPO_PATH}/tests/e2e"
354}
355
356function release_pass {
357	rm -f ./bin/etcd-last-release
358	# to grab latest patch release; bump this up for every minor release
359	UPGRADE_VER=$(git tag -l --sort=-version:refname "v3.3.*" | head -1)
360	if [ -n "$MANUAL_VER" ]; then
361		# in case, we need to test against different version
362		UPGRADE_VER=$MANUAL_VER
363	fi
364	if [[ -z ${UPGRADE_VER} ]]; then
365		UPGRADE_VER="v3.3.0"
366		echo "fallback to" ${UPGRADE_VER}
367	fi
368
369	local file="etcd-$UPGRADE_VER-linux-$GOARCH.tar.gz"
370	echo "Downloading $file"
371
372	set +e
373	curl --fail -L "https://github.com/etcd-io/etcd/releases/download/$UPGRADE_VER/$file" -o "/tmp/$file"
374	local result=$?
375	set -e
376	case $result in
377		0)	;;
378		*)	echo "--- FAIL:" ${result}
379			exit $result
380			;;
381	esac
382
383	tar xzvf "/tmp/$file" -C /tmp/ --strip-components=1
384	mkdir -p ./bin
385	mv /tmp/etcd ./bin/etcd-last-release
386}
387
388function shellcheck_pass {
389	if command -v shellcheck >/dev/null; then
390		shellcheckResult=$(shellcheck -fgcc build test scripts/*.sh 2>&1 || true)
391		if [ -n "${shellcheckResult}" ]; then
392			echo -e "shellcheck checking failed:\\n${shellcheckResult}"
393			exit 255
394		fi
395	fi
396}
397
398function markdown_you_pass {
399	# eschew you
400	yous=$(find . -name \*.md ! -path './vendor/*' ! -path './Documentation/v2/*' ! -path './gopath.proto/*' -exec grep -E --color "[Yy]ou[r]?[ '.,;]" {} + || true)
401	if [ -n "$yous" ]; then
402		echo -e "found 'you' in documentation:\\n${yous}"
403		exit 255
404	fi
405}
406
407function markdown_marker_pass {
408	# TODO: check other markdown files when marker handles headers with '[]'
409	if command -v marker >/dev/null; then
410		markerResult=$(marker --skip-http --root ./Documentation 2>&1 || true)
411		if [ -n "${markerResult}" ]; then
412			echo -e "marker checking failed:\\n${markerResult}"
413			exit 255
414		fi
415	else
416		echo "Skipping marker..."
417	fi
418}
419
420function goword_pass {
421	if command -v goword >/dev/null; then
422		# get all go files to process
423		gofiles=$(find "${FMT[@]}" -iname '*.go' 2>/dev/null)
424		# shellcheck disable=SC2206
425		gofiles_all=($gofiles)
426		# ignore tests and protobuf files
427		# shellcheck disable=SC1117
428		gofiles=$(echo "${gofiles_all[@]}" | sort | uniq | sed "s/ /\n/g" | grep -vE "(\\_test.go|\\.pb\\.go)")
429		# shellcheck disable=SC2206
430		gofiles=($gofiles)
431		# only check for broken exported godocs
432		gowordRes=$(goword -use-spell=false "${gofiles[@]}" | grep godoc-export | sort)
433		if [ -n "$gowordRes" ]; then
434			echo -e "goword checking failed:\\n${gowordRes}"
435			exit 255
436		fi
437		# check some spelling
438		gowordRes=$(goword -ignore-file=.words clientv3/{*,*/*}.go 2>&1 | grep spell | sort)
439		if [ -n "$gowordRes" ]; then
440			echo -e "goword checking failed:\\n${gowordRes}"
441			exit 255
442		fi
443	else
444		echo "Skipping goword..."
445	fi
446}
447
448function gofmt_pass {
449	fmtRes=$(gofmt -l -s -d "${FMT[@]}")
450	if [ -n "${fmtRes}" ]; then
451		echo -e "gofmt checking failed:\\n${fmtRes}"
452		exit 255
453	fi
454}
455
456function govet_pass {
457	vetRes=$(go vet "${TEST[@]}")
458	if [ -n "${vetRes}" ]; then
459		echo -e "govet checking failed:\\n${vetRes}"
460		exit 255
461	fi
462}
463
464function govet_shadow_pass {
465	fmtpkgs=$(for a in "${FMT[@]}"; do dirname "$a"; done | sort | uniq | grep -v "\\.")
466	# shellcheck disable=SC2206
467	fmtpkgs=($fmtpkgs)
468	# Golang 1.12 onwards the experimental -shadow option is no longer available with go vet
469	go get golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
470	export PATH=${GOPATH}/bin:${PATH}
471	shadow_tool=$(which shadow)
472	vetRes=$(go vet -all -vettool="${shadow_tool}" "${TEST[@]}")
473	if [ -n "${vetRes}" ]; then
474		echo -e "govet -all -shadow checking failed:\\n${vetRes}"
475		exit 255
476	fi
477}
478
479function unparam_pass {
480	if command -v unparam >/dev/null; then
481		unparamResult=$(unparam "${STATIC_ANALYSIS_PATHS[@]}" 2>&1 || true)
482		if [ -n "${unparamResult}" ]; then
483			echo -e "unparam checking failed:\\n${unparamResult}"
484			exit 255
485		fi
486	else
487		echo "Skipping unparam..."
488	fi
489}
490
491function staticcheck_pass {
492	if command -v staticcheck >/dev/null; then
493		staticcheckResult=$(staticcheck "${STATIC_ANALYSIS_PATHS[@]}" 2>&1 || true)
494		if [ -n "${staticcheckResult}" ]; then
495			# TODO: resolve these after go1.8 migration
496			# See https://github.com/dominikh/go-tools/tree/master/cmd/staticcheck
497			STATIC_CHECK_MASK="S(A|T)(1002|1005|1006|1008|1012|1019|1032|2002|4003|4006)"
498			if echo "${staticcheckResult}" | grep -vE "$STATIC_CHECK_MASK"; then
499				echo -e "staticcheck checking failed:\\n${staticcheckResult}"
500				exit 255
501			else
502				suppressed=$(echo "${staticcheckResult}" | sed 's/ /\n/g' | grep "(SA" | sort | uniq -c)
503				echo -e "staticcheck suppressed warnings:\\n${suppressed}"
504			fi
505		fi
506	else
507		echo "Skipping staticcheck..."
508	fi
509}
510
511function revive_pass {
512	if command -v revive >/dev/null; then
513		reviveResult=$(revive -config ./tests/revive.toml -exclude "vendor/..." ./... 2>&1 || true)
514		if [ -n "${reviveResult}" ]; then
515			echo -e "revive checking failed:\\n${reviveResult}"
516			exit 255
517		fi
518	else
519		echo "Skipping revive..."
520	fi
521}
522
523function unconvert_pass {
524	if command -v unconvert >/dev/null; then
525		unconvertResult=$(unconvert -v "${STATIC_ANALYSIS_PATHS[@]}" 2>&1 || true)
526		if [ -n "${unconvertResult}" ]; then
527			echo -e "unconvert checking failed:\\n${unconvertResult}"
528			exit 255
529		fi
530	else
531		echo "Skipping unconvert..."
532	fi
533}
534
535function ineffassign_pass {
536	if command -v ineffassign >/dev/null; then
537		ineffassignResult=$(ineffassign "${STATIC_ANALYSIS_PATHS[@]}" 2>&1 || true)
538		if [ -n "${ineffassignResult}" ]; then
539			echo -e "ineffassign checking failed:\\n${ineffassignResult}"
540			exit 255
541		fi
542	else
543		echo "Skipping ineffassign..."
544	fi
545}
546
547function nakedret_pass {
548	if command -v nakedret >/dev/null; then
549		nakedretResult=$(nakedret "${STATIC_ANALYSIS_PATHS[@]}" 2>&1 || true)
550		if [ -n "${nakedretResult}" ]; then
551			echo -e "nakedret checking failed:\\n${nakedretResult}"
552			exit 255
553		fi
554	else
555		echo "Skipping nakedret..."
556	fi
557}
558
559function license_header_pass {
560	licRes=""
561	files=$(find . -type f -iname '*.go' ! -path './vendor/*' ! -path './gopath.proto/*')
562	for file in $files; do
563		if ! head -n3 "${file}" | grep -Eq "(Copyright|generated|GENERATED)" ; then
564			licRes="${licRes}"$(echo -e "  ${file}")
565		fi
566	done
567	if [ -n "${licRes}" ]; then
568		echo -e "license header checking failed:\\n${licRes}"
569		exit 255
570	fi
571}
572
573function receiver_name_pass {
574	# shellcheck disable=SC1117
575	recvs=$(grep 'func ([^*]' {*,*/*,*/*/*}.go  | grep -Ev "(generated|pb/)" | tr  ':' ' ' |  \
576		awk ' { print $2" "$3" "$4" "$1 }' | sed "s/[a-zA-Z\.]*go//g" |  sort  | uniq  | \
577		grep -Ev  "(Descriptor|Proto|_)"  | awk ' { print $3" "$4 } ' | sort | uniq -c | grep -v ' 1 ' | awk ' { print $2 } ')
578	if [ -n "${recvs}" ]; then
579		# shellcheck disable=SC2206
580		recvs=($recvs)
581		for recv in "${recvs[@]}"; do
582			echo "Mismatched receiver for $recv..."
583			grep "$recv" "${FMT[@]}" | grep 'func ('
584		done
585		exit 255
586	fi
587}
588
589function commit_title_pass {
590	git log --oneline "$(git merge-base HEAD "$(git rev-parse --abbrev-ref --symbolic-full-name "@{u}")")"...HEAD | while read -r l; do
591		commitMsg=$(echo "$l" | cut -f2- -d' ')
592		if [[ "$commitMsg" == Merge* ]]; then
593			# ignore "Merge pull" commits
594			continue
595		fi
596		if [[ "$commitMsg" == Revert* ]]; then
597			# ignore revert commits
598			continue
599		fi
600
601		pkgPrefix=$(echo "$commitMsg" | cut -f1 -d':')
602		spaceCommas=$(echo "$commitMsg" | sed 's/ /\n/g' | grep -c ',$' || echo 0)
603		commaSpaces=$(echo "$commitMsg" | sed 's/,/\n/g' | grep -c '^ ' || echo 0)
604		if [[ $(echo "$commitMsg" | grep -c ":..*") == 0 || "$commitMsg" == "$pkgPrefix" || "$spaceCommas" != "$commaSpaces" ]]; then
605			echo "$l"...
606			echo "Expected commit title format '<package>{\", \"<package>}: <description>'"
607			echo "Got: $l"
608			exit 255
609		fi
610	done
611}
612
613# tools gosimple,unused,staticheck,unconvert,ineffasign,nakedret
614# are not module-aware. See https://github.com/golang/go/issues/24661
615# The module-aware versions need to be used when they become available
616function fmt_pass {
617	toggle_failpoints disable
618
619    # TODO: add "unparam"
620	for p in shellcheck \
621			markdown_you \
622			markdown_marker \
623			goword \
624			gofmt \
625			govet \
626			govet_shadow \
627			revive \
628			license_header \
629			receiver_name \
630			commit_title \
631			; do
632		echo "'$p' started at $(date)"
633		"${p}"_pass "$@"
634		echo "'$p' completed at $(date)"
635	done
636}
637
638function bom_pass {
639	if ! command -v license-bill-of-materials >/dev/null; then
640		return
641	fi
642	if [ "${GO111MODULE}" == "on" ]; then
643		# license-bill-off-materials calls "go list std cmd" which cannot handle modules
644		# Please see https://github.com/golang/go/issues/26924
645		echo "Skipping license-bill-of-materials with go modules..."
646		return
647	fi
648	echo "Checking bill of materials..."
649	license-bill-of-materials \
650		--override-file bill-of-materials.override.json \
651		go.etcd.io/etcd go.etcd.io/etcd/etcdctl >bom-now.json || true
652	if ! diff bill-of-materials.json bom-now.json; then
653		echo "vendored licenses do not match given bill of materials"
654		exit 255
655	fi
656	rm bom-now.json
657}
658
659function dep_pass {
660	echo "Checking package dependencies..."
661	# don't pull in etcdserver package
662	pushd clientv3 >/dev/null
663	badpkg="(etcdserver$|mvcc$|backend$|grpc-gateway)"
664	deps=$(go list -f '{{ .Deps }}'  | sed 's/ /\n/g' | grep -E "${badpkg}" || echo "")
665	popd >/dev/null
666	if [ -n "$deps" ]; then
667		echo -e "clientv3 has masked dependencies:\\n${deps}"
668		exit 255
669	fi
670}
671
672function build_cov_pass {
673	out="bin"
674	if [ -n "${BINDIR}" ]; then out="${BINDIR}"; fi
675	go test -tags cov -c -covermode=set -coverpkg="$PKGS_COMMA" -o "${out}/etcd_test"
676	go test -tags cov -c -covermode=set -coverpkg="$PKGS_COMMA" -o "${out}/etcdctl_test" "${REPO_PATH}/etcdctl"
677}
678
679# fail fast on static tests
680function build_pass {
681	echo "Checking build..."
682	GO_BUILD_FLAGS="-v" etcd_build
683	GO_BUILD_FLAGS="-v" tools_build
684}
685
686for pass in $PASSES; do
687	echo "Starting '$pass' pass at $(date)"
688	"${pass}"_pass "$@"
689	echo "Finished '$pass' pass at $(date)"
690done
691
692echo "Success"
693
694