-
-
Notifications
You must be signed in to change notification settings - Fork 612
/
test.sh
executable file
·289 lines (249 loc) · 9.08 KB
/
test.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#!/usr/bin/env bash
# -e Stops execution in the instance of a command or pipeline error
# -u Treat unset variables as an error and exit immediately
set -eu
if type realpath >/dev/null 2>&1 ; then
cd "$(realpath -- $(dirname -- "$0"))"
fi
#
# Defaults
#
export RACE="false"
STAGE="starting"
STATUS="FAILURE"
RUN=()
UNIT_PACKAGES=()
UNIT_FLAGS=()
FILTER=()
#
# Cleanup Functions
#
function flush_redis() {
go run ./test/boulder-tools/flushredis/main.go
}
#
# Print Functions
#
function print_outcome() {
if [ "$STATUS" == SUCCESS ]
then
echo -e "\e[32m"$STATUS"\e[0m"
else
echo -e "\e[31m"$STATUS"\e[0m while running \e[31m"$STAGE"\e[0m"
fi
}
function print_list_of_integration_tests() {
go test -tags integration -list=. ./test/integration/... | grep '^Test'
exit 0
}
function exit_msg() {
# complain to STDERR and exit with error
echo "$*" >&2
exit 2
}
function check_arg() {
if [ -z "$OPTARG" ]
then
exit_msg "No arg for --$OPT option, use: -h for help">&2
fi
}
function print_usage_exit() {
echo "$USAGE"
exit 0
}
function print_heading {
echo
echo -e "\e[34m\e[1m"$1"\e[0m"
}
function run_and_expect_silence() {
echo "$@"
result_file=$(mktemp -t bouldertestXXXX)
"$@" 2>&1 | tee "${result_file}"
# Fail if result_file is nonempty.
if [ -s "${result_file}" ]; then
rm "${result_file}"
exit 1
fi
rm "${result_file}"
}
#
# Testing Helpers
#
function run_unit_tests() {
go test "${UNIT_FLAGS[@]}" "${UNIT_PACKAGES[@]}" "${FILTER[@]}"
}
#
# Main CLI Parser
#
USAGE="$(cat -- <<-EOM
Usage:
Boulder test suite CLI, intended to be run inside of a Docker container:
docker compose run --use-aliases boulder ./$(basename "${0}") [OPTION]...
With no options passed, runs standard battery of tests (lint, unit, and integration)
-l, --lints Adds lint to the list of tests to run
-u, --unit Adds unit to the list of tests to run
-v, --unit-verbose Enables verbose output for unit tests
-w, --unit-without-cache Disables go test caching for unit tests
-p <DIR>, --unit-test-package=<DIR> Run unit tests for specific go package(s)
-e, --enable-race-detection Enables race detection for unit and integration tests
-n, --config-next Changes BOULDER_CONFIG_DIR from test/config to test/config-next
-i, --integration Adds integration to the list of tests to run
-s, --start-py Adds start to the list of tests to run
-g, --generate Adds generate to the list of tests to run
-o, --list-integration-tests Outputs a list of the available integration tests
-f <REGEX>, --filter=<REGEX> Run only those tests matching the regular expression
Note:
This option disables the '"back in time"' integration test setup
For tests, the regular expression is split by unbracketed slash (/)
characters into a sequence of regular expressions
Example:
TestAkamaiPurgerDrainQueueFails/TestWFECORS
-h, --help Shows this help message
EOM
)"
while getopts luvweciosmgnhp:f:-: OPT; do
if [ "$OPT" = - ]; then # long option: reformulate OPT and OPTARG
OPT="${OPTARG%%=*}" # extract long option name
OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty)
OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=`
fi
case "$OPT" in
l | lints ) RUN+=("lints") ;;
u | unit ) RUN+=("unit") ;;
v | unit-verbose ) UNIT_FLAGS+=("-v") ;;
w | unit-without-cache ) UNIT_FLAGS+=("-count=1") ;;
p | unit-test-package ) check_arg; UNIT_PACKAGES+=("${OPTARG}") ;;
e | enable-race-detection ) RACE="true"; UNIT_FLAGS+=("-race") ;;
i | integration ) RUN+=("integration") ;;
o | list-integration-tests ) print_list_of_integration_tests ;;
f | filter ) check_arg; FILTER+=("${OPTARG}") ;;
s | start-py ) RUN+=("start") ;;
g | generate ) RUN+=("generate") ;;
n | config-next ) BOULDER_CONFIG_DIR="test/config-next" ;;
h | help ) print_usage_exit ;;
??* ) exit_msg "Illegal option --$OPT" ;; # bad long option
? ) exit 2 ;; # bad short option (error reported via getopts)
esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list
# The list of segments to run. Order doesn't matter.
if [ -z "${RUN[@]+x}" ]
then
RUN+=("lints" "unit" "integration")
fi
# Filter is used by unit and integration but should not be used for both at the same time
if [[ "${RUN[@]}" =~ unit ]] && [[ "${RUN[@]}" =~ integration ]] && [[ -n "${FILTER[@]+x}" ]]
then
exit_msg "Illegal option: (-f, --filter) when specifying both (-u, --unit) and (-i, --integration)"
fi
# If unit + filter: set correct flags for go test
if [[ "${RUN[@]}" =~ unit ]] && [[ -n "${FILTER[@]+x}" ]]
then
FILTER=(--test.run "${FILTER[@]}")
fi
# If integration + filter: set correct flags for test/integration-test.py
if [[ "${RUN[@]}" =~ integration ]] && [[ -n "${FILTER[@]+x}" ]]
then
FILTER=(--filter "${FILTER[@]}")
fi
# If unit test packages are not specified: set flags to run unit tests
# for all boulder packages
if [ -z "${UNIT_PACKAGES[@]+x}" ]
then
# '-p=1' configures unit tests to run serially, rather than in parallel. Our
# unit tests depend on mutating a database and then cleaning up after
# themselves. If these test were run in parallel, they could fail spuriously
# due to one test modifying a table (especially registrations) while another
# test is reading from it.
# https://github.com/letsencrypt/boulder/issues/1499
# https://pkg.go.dev/cmd/go#hdr-Testing_flags
UNIT_FLAGS+=("-p=1")
UNIT_PACKAGES+=("./...")
fi
print_heading "Boulder Test Suite CLI"
print_heading "Settings:"
# On EXIT, trap and print outcome
trap "print_outcome" EXIT
settings="$(cat -- <<-EOM
RUN: ${RUN[@]}
BOULDER_CONFIG_DIR: $BOULDER_CONFIG_DIR
GOCACHE: $(go env GOCACHE)
UNIT_PACKAGES: ${UNIT_PACKAGES[@]}
UNIT_FLAGS: ${UNIT_FLAGS[@]}
FILTER: ${FILTER[@]}
EOM
)"
echo "$settings"
print_heading "Starting..."
#
# Run various linters.
#
STAGE="lints"
if [[ "${RUN[@]}" =~ "$STAGE" ]] ; then
print_heading "Running Lints"
golangci-lint run --timeout 9m ./...
# Implicitly loads staticcheck.conf from the root of the boulder repository
staticcheck ./...
python3 test/grafana/lint.py
# Check for common spelling errors using typos.
# Update .typos.toml if you find false positives
run_and_expect_silence typos
# Check test JSON configs are formatted consistently
run_and_expect_silence ./test/format-configs.py 'test/config*/*.json'
fi
#
# Unit Tests.
#
STAGE="unit"
if [[ "${RUN[@]}" =~ "$STAGE" ]] ; then
print_heading "Running Unit Tests"
flush_redis
run_unit_tests
fi
#
# Integration tests
#
STAGE="integration"
if [[ "${RUN[@]}" =~ "$STAGE" ]] ; then
print_heading "Running Integration Tests"
flush_redis
python3 test/integration-test.py --chisel --gotest "${FILTER[@]}"
fi
# Test that just ./start.py works, which is a proxy for testing that
# `docker compose up` works, since that just runs start.py (via entrypoint.sh).
STAGE="start"
if [[ "${RUN[@]}" =~ "$STAGE" ]] ; then
print_heading "Running Start Test"
python3 start.py &
for I in {1..115}; do
sleep 1
curl -s http://localhost:4001/directory && echo "Boulder took ${I} seconds to come up" && break
done
if [ "${I}" -eq 115 ]; then
echo "Boulder did not come up after ${I} seconds during ./start.py."
exit 1
fi
fi
# Run generate to make sure all our generated code can be re-generated with
# current tools.
# Note: Some of the tools we use seemingly don't understand ./vendor yet, and
# so will fail if imports are not available in $GOPATH.
STAGE="generate"
if [[ "${RUN[@]}" =~ "$STAGE" ]] ; then
print_heading "Running Generate"
# Additionally, we need to run go install before go generate because the stringer command
# (using in ./grpc/) checks imports, and depends on the presence of a built .a
# file to determine an import really exists. See
# https://golang.org/src/go/internal/gcimporter/gcimporter.go#L30
# Without this, we get error messages like:
# stringer: checking package: grpc/bcodes.go:6:2: could not import
# github.com/letsencrypt/boulder/probs (can't find import:
# github.com/letsencrypt/boulder/probs)
go install ./probs
go install ./vendor/google.golang.org/grpc/codes
run_and_expect_silence go generate ./...
run_and_expect_silence git diff --exit-code .
fi
# Because set -e stops execution in the instance of a command or pipeline
# error; if we got here we assume success
STATUS="SUCCESS"