forked from letsencrypt/boulder
-
Notifications
You must be signed in to change notification settings - Fork 0
/
test.sh
executable file
·294 lines (255 loc) · 9.47 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
290
291
292
293
294
#!/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=()
FILTER=()
#
# 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() {
if [ "${RACE}" == true ]; then
# Run the full suite of tests once with the -race flag.
go test -race "${UNIT_PACKAGES[@]}" "${FILTER[@]}"
else
# When running locally, we skip the -race flag for speedier test runs. We
# also pass -p 1 to require the tests to run serially instead of in
# parallel. This is because our unittests depend on mutating a database and
# then cleaning up after themselves. If they run in parallel, they can fail
# spuriously because one test is modifying a table (especially
# registrations) while another test is reading it.
# https://github.com/letsencrypt/boulder/issues/1499
go test "${UNIT_PACKAGES[@]}" "${FILTER[@]}"
fi
}
#
# 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
-p <DIR>, --unit-test-package=<DIR> Run unit tests for specific go package(s)
-e, --enable-race-detection Enables -race flag for all 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
-v, --gomod-vendor Adds gomod-vendor 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 lueciosvgnhp: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") ;;
p | unit-test-package ) check_arg; UNIT_PACKAGES+=("${OPTARG}") ;;
e | enable-race-detection ) RACE="true" ;;
i | integration ) RUN+=("integration") ;;
o | list-integration-tests ) print_list_of_integration_tests ;;
f | filter ) check_arg; FILTER+=("${OPTARG}") ;;
s | start-py ) RUN+=("start") ;;
v | gomod-vendor ) RUN+=("gomod-vendor") ;;
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. Note: gomod-vendor
# is specifically left out of the defaults, because we don't want to run
# it locally (it could delete local state).
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
UNIT_PACKAGES+=("-p" "1" "./...")
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
UNIT_PACKAGES: ${UNIT_PACKAGES[@]}
RACE: $RACE
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 ./...
python3 test/grafana/lint.py
# Check for common spelling errors using codespell.
# Update .codespell.ignore.txt if you find false positives (NOTE: ignored
# words should be all lowercase).
run_and_expect_silence codespell \
--ignore-words=.codespell.ignore.txt \
--skip=.git,.gocache,go.sum,go.mod,vendor,bin,*.pyc,*.pem,*.der,*.resp,*.req,*.csr,.codespell.ignore.txt,.*.swp
# Check test JSON configs are formatted consistently
./test/format-configs.py 'test/config*/*.json'
run_and_expect_silence git diff --exit-code .
fi
#
# Unit Tests.
#
STAGE="unit"
if [[ "${RUN[@]}" =~ "$STAGE" ]] ; then
print_heading "Running Unit Tests"
run_unit_tests
fi
#
# Integration tests
#
STAGE="integration"
if [[ "${RUN[@]}" =~ "$STAGE" ]] ; then
print_heading "Running Integration Tests"
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 $(seq 1 100); do
sleep 1
curl -s http://localhost:4001/directory && break
done
if [[ "$I" = 100 ]]; then
echo "Boulder did not come up after ./start.py."
exit 1
fi
fi
# Run go mod vendor (happens only in CI) to check that the versions in
# vendor/ really exist in the remote repo and match what we have.
STAGE="gomod-vendor"
if [[ "${RUN[@]}" =~ "$STAGE" ]] ; then
print_heading "Running Go Mod Tidy"
go mod tidy
print_heading "Running Go Mod Vendor"
go mod vendor
run_and_expect_silence git diff --exit-code .
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"