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