1 #!/usr/bin/env bash 2 # Copyright 2016 The TensorFlow Authors. All Rights Reserved. 3 # 4 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # you may not use this file except in compliance with the License. 6 # You may obtain a copy of the License at 7 # 8 # http://www.apache.org/licenses/LICENSE-2.0 9 # 10 # Unless required by applicable law or agreed to in writing, software 11 # distributed under the License is distributed on an "AS IS" BASIS, 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 # ============================================================================== 16 # 17 # Usage: ci_sanity.sh [--pep8] [--incremental] [bazel flags] 18 # 19 # Options: 20 # run sanity checks: python 2&3 pylint checks and bazel nobuild 21 # --pep8 run pep8 test only 22 # --incremental Performs checks incrementally, by using the files changed in 23 # the latest commit 24 25 # Current script directory 26 SCRIPT_DIR=$( cd ${0%/*} && pwd -P ) 27 source "${SCRIPT_DIR}/builds/builds_common.sh" 28 29 ROOT_DIR=$( cd "$SCRIPT_DIR/../../.." && pwd -P ) 30 31 # Helper functions 32 die() { 33 echo $@ 34 exit 1 35 } 36 37 num_cpus() { 38 # Get the number of CPUs 39 N_CPUS=$(grep -c ^processor /proc/cpuinfo) 40 if [[ -z ${N_CPUS} ]]; then 41 die "ERROR: Unable to determine the number of CPUs" 42 fi 43 44 echo ${N_CPUS} 45 } 46 47 # Helper functions for examining changed files in the last non-merge git 48 # commit. 49 50 # Get the hash of the last non-merge git commit on the current branch. 51 # Usage: get_last_non_merge_git_commit 52 get_last_non_merge_git_commit() { 53 git rev-list --no-merges -n 1 HEAD 54 } 55 56 # List files changed (i.e., added, removed or revised) in the last non-merge 57 # git commit. 58 # Usage: get_changed_files_in_last_non_merge_git_commit 59 get_changed_files_in_last_non_merge_git_commit() { 60 git diff-tree --no-commit-id --name-only -r $(get_last_non_merge_git_commit) 61 } 62 63 # List Python files changed in the last non-merge git commit that still exist, 64 # i.e., not removed. 65 # Usage: get_py_files_to_check [--incremental] 66 get_py_files_to_check() { 67 if [[ "$1" == "--incremental" ]]; then 68 CHANGED_PY_FILES=$(get_changed_files_in_last_non_merge_git_commit | \ 69 grep '.*\.py$') 70 71 # Do not include files removed in the last non-merge commit. 72 PY_FILES="" 73 for PY_FILE in ${CHANGED_PY_FILES}; do 74 if [[ -f "${PY_FILE}" ]]; then 75 PY_FILES="${PY_FILES} ${PY_FILE}" 76 fi 77 done 78 79 echo "${PY_FILES}" 80 else 81 find tensorflow -name '*.py' 82 fi 83 } 84 85 # Subfunctions for substeps 86 # Run pylint 87 do_pylint() { 88 # Usage: do_pylint (PYTHON2 | PYTHON3) [--incremental] 89 # 90 # Options: 91 # --incremental Performs check on only the python files changed in the 92 # last non-merge git commit. 93 94 # Use this list to whitelist pylint errors 95 ERROR_WHITELIST="^tensorflow/python/framework/function_test\.py.*\[E1123.*noinline "\ 96 "^tensorflow/python/platform/default/_gfile\.py.*\[E0301.*non-iterator "\ 97 "^tensorflow/python/platform/default/_googletest\.py.*\[E0102.*function\salready\sdefined "\ 98 "^tensorflow/python/feature_column/feature_column_test\.py.*\[E0110.*abstract-class-instantiated "\ 99 "^tensorflow/contrib/layers/python/layers/feature_column\.py.*\[E0110.*abstract-class-instantiated "\ 100 "^tensorflow/contrib/eager/python/evaluator\.py.*\[E0202.*method-hidden "\ 101 "^tensorflow/contrib/eager/python/metrics_impl\.py.*\[E0202.*method-hidden "\ 102 "^tensorflow/python/platform/gfile\.py.*\[E0301.*non-iterator "\ 103 "^tensorflow/python/keras/_impl/keras/callbacks\.py.*\[E1133.*not-an-iterable "\ 104 "^tensorflow/python/keras/_impl/keras/layers/recurrent\.py.*\[E0203.*access-member-before-definition "\ 105 "^tensorflow/python/kernel_tests/constant_op_eager_test.py.*\[E0303.*invalid-length-returned" 106 107 echo "ERROR_WHITELIST=\"${ERROR_WHITELIST}\"" 108 109 if [[ $# != "1" ]] && [[ $# != "2" ]]; then 110 echo "Invalid syntax when invoking do_pylint" 111 echo "Usage: do_pylint (PYTHON2 | PYTHON3) [--incremental]" 112 return 1 113 fi 114 115 if [[ $1 == "PYTHON2" ]]; then 116 PYLINT_BIN="python -m pylint" 117 elif [[ $1 == "PYTHON3" ]]; then 118 PYLINT_BIN="python3 -m pylint" 119 else 120 echo "Unrecognized python version (PYTHON2 | PYTHON3): $1" 121 return 1 122 fi 123 124 if [[ "$2" == "--incremental" ]]; then 125 PYTHON_SRC_FILES=$(get_py_files_to_check --incremental) 126 127 if [[ -z "${PYTHON_SRC_FILES}" ]]; then 128 echo "do_pylint will NOT run due to --incremental flag and due to the "\ 129 "absence of Python code changes in the last commit." 130 return 0 131 else 132 # For incremental builds, we still check all Python files in cases there 133 # are function signature changes that affect unchanged Python files. 134 PYTHON_SRC_FILES=$(get_py_files_to_check) 135 fi 136 elif [[ -z "$2" ]]; then 137 PYTHON_SRC_FILES=$(get_py_files_to_check) 138 else 139 echo "Invalid syntax for invoking do_pylint" 140 echo "Usage: do_pylint (PYTHON2 | PYTHON3) [--incremental]" 141 return 1 142 fi 143 144 if [[ -z ${PYTHON_SRC_FILES} ]]; then 145 echo "do_pylint found no Python files to check. Returning." 146 return 0 147 fi 148 149 PYLINTRC_FILE="${SCRIPT_DIR}/pylintrc" 150 151 if [[ ! -f "${PYLINTRC_FILE}" ]]; then 152 die "ERROR: Cannot find pylint rc file at ${PYLINTRC_FILE}" 153 fi 154 155 NUM_SRC_FILES=$(echo ${PYTHON_SRC_FILES} | wc -w) 156 NUM_CPUS=$(num_cpus) 157 158 echo "Running pylint on ${NUM_SRC_FILES} files with ${NUM_CPUS} "\ 159 "parallel jobs..." 160 echo "" 161 162 PYLINT_START_TIME=$(date +'%s') 163 OUTPUT_FILE="$(mktemp)_pylint_output.log" 164 ERRORS_FILE="$(mktemp)_pylint_errors.log" 165 NONWL_ERRORS_FILE="$(mktemp)_pylint_nonwl_errors.log" 166 167 rm -rf ${OUTPUT_FILE} 168 rm -rf ${ERRORS_FILE} 169 rm -rf ${NONWL_ERRORS_FILE} 170 touch ${NONWL_ERRORS_FILE} 171 172 ${PYLINT_BIN} --rcfile="${PYLINTRC_FILE}" --output-format=parseable \ 173 --jobs=${NUM_CPUS} ${PYTHON_SRC_FILES} > ${OUTPUT_FILE} 2>&1 174 PYLINT_END_TIME=$(date +'%s') 175 176 echo "" 177 echo "pylint took $((PYLINT_END_TIME - PYLINT_START_TIME)) s" 178 echo "" 179 180 # Report only what we care about 181 # Ref https://pylint.readthedocs.io/en/latest/technical_reference/features.html 182 # E: all errors 183 # W0311 bad-indentation 184 # W0312 mixed-indentation 185 # C0330 bad-continuation 186 # C0301 line-too-long 187 # C0326 bad-whitespace 188 # W0611 unused-import 189 # W0622 redefined-builtin 190 grep -E '(\[E|\[W0311|\[W0312|\[C0330|\[C0301|\[C0326|\[W0611|\[W0622)' ${OUTPUT_FILE} > ${ERRORS_FILE} 191 192 N_ERRORS=0 193 while read -r LINE; do 194 IS_WHITELISTED=0 195 for WL_REGEX in ${ERROR_WHITELIST}; do 196 if echo ${LINE} | grep -q "${WL_REGEX}"; then 197 echo "Found a whitelisted error:" 198 echo " ${LINE}" 199 IS_WHITELISTED=1 200 fi 201 done 202 203 if [[ ${IS_WHITELISTED} == "0" ]]; then 204 echo "${LINE}" >> ${NONWL_ERRORS_FILE} 205 echo "" >> ${NONWL_ERRORS_FILE} 206 ((N_ERRORS++)) 207 fi 208 done <${ERRORS_FILE} 209 210 echo "" 211 if [[ ${N_ERRORS} != 0 ]]; then 212 echo "FAIL: Found ${N_ERRORS} non-whitelited pylint errors:" 213 cat "${NONWL_ERRORS_FILE}" 214 return 1 215 else 216 echo "PASS: No non-whitelisted pylint errors were found." 217 return 0 218 fi 219 } 220 221 # Run pep8 check 222 do_pep8() { 223 # Usage: do_pep8 [--incremental] 224 # Options: 225 # --incremental Performs check on only the python files changed in the 226 # last non-merge git commit. 227 228 PEP8_BIN="/usr/local/bin/pep8" 229 PEP8_CONFIG_FILE="${SCRIPT_DIR}/pep8" 230 231 if [[ "$1" == "--incremental" ]]; then 232 PYTHON_SRC_FILES=$(get_py_files_to_check --incremental) 233 NUM_PYTHON_SRC_FILES=$(echo ${PYTHON_SRC_FILES} | wc -w) 234 235 echo "do_pep8 will perform checks on only the ${NUM_PYTHON_SRC_FILES} "\ 236 "Python file(s) changed in the last non-merge git commit due to the "\ 237 "--incremental flag:" 238 echo "${PYTHON_SRC_FILES}" 239 echo "" 240 else 241 PYTHON_SRC_FILES=$(get_py_files_to_check) 242 fi 243 244 if [[ -z ${PYTHON_SRC_FILES} ]]; then 245 echo "do_pep8 found no Python files to check. Returning." 246 return 0 247 fi 248 249 if [[ ! -f "${PEP8_CONFIG_FILE}" ]]; then 250 die "ERROR: Cannot find pep8 config file at ${PEP8_CONFIG_FILE}" 251 fi 252 echo "See \"${PEP8_CONFIG_FILE}\" for pep8 config( e.g., ignored errors)" 253 254 NUM_SRC_FILES=$(echo ${PYTHON_SRC_FILES} | wc -w) 255 256 echo "Running pep8 on ${NUM_SRC_FILES} files" 257 echo "" 258 259 PEP8_START_TIME=$(date +'%s') 260 PEP8_OUTPUT_FILE="$(mktemp)_pep8_output.log" 261 262 rm -rf ${PEP8_OUTPUT_FILE} 263 264 ${PEP8_BIN} --config="${PEP8_CONFIG_FILE}" --statistics \ 265 ${PYTHON_SRC_FILES} 2>&1 | tee ${PEP8_OUTPUT_FILE} 266 PEP8_END_TIME=$(date +'%s') 267 268 echo "" 269 echo "pep8 took $((PEP8_END_TIME - PEP8_START_TIME)) s" 270 echo "" 271 272 if [[ -s ${PEP8_OUTPUT_FILE} ]]; then 273 echo "FAIL: pep8 found above errors and/or warnings." 274 return 1 275 else 276 echo "PASS: No pep8 errors or warnings were found" 277 return 0 278 fi 279 } 280 281 282 do_buildifier(){ 283 BUILD_FILES=$(find tensorflow -name 'BUILD*') 284 NUM_BUILD_FILES=$(echo ${BUILD_FILES} | wc -w) 285 286 echo "Running do_buildifier on ${NUM_BUILD_FILES} files" 287 echo "" 288 289 BUILDIFIER_START_TIME=$(date +'%s') 290 BUILDIFIER_OUTPUT_FILE="$(mktemp)_buildifier_output.log" 291 292 rm -rf ${BUILDIFIER_OUTPUT_FILE} 293 294 buildifier -showlog -v -mode=check \ 295 ${BUILD_FILES} 2>&1 | tee ${BUILDIFIER_OUTPUT_FILE} 296 BUILDIFIER_END_TIME=$(date +'%s') 297 298 echo "" 299 echo "buildifier took $((BUILDIFIER_END_TIME - BUILDIFIER_START_TIME)) s" 300 echo "" 301 302 if [[ -s ${BUILDIFIER_OUTPUT_FILE} ]]; then 303 echo "FAIL: buildifier found errors and/or warnings in above BUILD files." 304 echo "buildifier suggested the following changes:" 305 buildifier -showlog -v -mode=diff ${BUILD_FILES} 306 echo "Please fix manually or run buildifier <file> to auto-fix." 307 return 1 308 else 309 echo "PASS: No buildifier errors or warnings were found" 310 return 0 311 fi 312 } 313 314 do_external_licenses_check(){ 315 BUILD_TARGET="$1" 316 LICENSES_TARGET="$2" 317 318 EXTERNAL_LICENSES_CHECK_START_TIME=$(date +'%s') 319 320 EXTERNAL_DEPENDENCIES_FILE="$(mktemp)_external_dependencies.log" 321 LICENSES_FILE="$(mktemp)_licenses.log" 322 MISSING_LICENSES_FILE="$(mktemp)_missing_licenses.log" 323 EXTRA_LICENSES_FILE="$(mktemp)_extra_licenses.log" 324 325 echo "Getting external dependencies for ${BUILD_TARGET}" 326 bazel query "attr('licenses', 'notice', deps(${BUILD_TARGET}))" --keep_going \ 327 | grep -E -v "^//tensorflow" \ 328 | sed -e 's|:.*||' \ 329 | sort \ 330 | uniq 2>&1 \ 331 | tee ${EXTERNAL_DEPENDENCIES_FILE} 332 333 echo 334 echo "Getting list of external licenses mentioned in ${LICENSES_TARGET}." 335 bazel query "deps(${LICENSES_TARGET})" --keep_going \ 336 | grep -E -v "^//tensorflow" \ 337 | sed -e 's|:.*||' \ 338 | sort \ 339 | uniq 2>&1 \ 340 | tee ${LICENSES_FILE} 341 342 echo 343 comm -1 -3 ${EXTERNAL_DEPENDENCIES_FILE} ${LICENSES_FILE} 2>&1 | tee ${EXTRA_LICENSES_FILE} 344 echo 345 comm -2 -3 ${EXTERNAL_DEPENDENCIES_FILE} ${LICENSES_FILE} 2>&1 | tee ${MISSING_LICENSES_FILE} 346 347 EXTERNAL_LICENSES_CHECK_END_TIME=$(date +'%s') 348 349 # Blacklist 350 echo ${MISSING_LICENSES_FILE} 351 grep -e "@bazel_tools//third_party/" -e "@com_google_absl//absl" -e "@org_tensorflow//" -v ${MISSING_LICENSES_FILE} > temp.txt 352 mv temp.txt ${MISSING_LICENSES_FILE} 353 354 # Whitelist 355 echo ${EXTRA_LICENSE_FILE} 356 grep -e "@bazel_tools//src" -e "@bazel_tools//tools/" -e "@com_google_absl//" -e "//external" -e "@local" -v ${EXTRA_LICENSES_FILE} > temp.txt 357 mv temp.txt ${EXTRA_LICENSES_FILE} 358 359 360 361 echo 362 echo "do_external_licenses_check took $((EXTERNAL_LICENSES_CHECK_END_TIME - EXTERNAL_LICENSES_CHECK_START_TIME)) s" 363 echo 364 365 if [[ -s ${MISSING_LICENSES_FILE} ]] || [[ -s ${EXTRA_LICENSES_FILE} ]] ; then 366 echo "FAIL: mismatch in packaged licenses and external dependencies" 367 if [[ -s ${MISSING_LICENSES_FILE} ]] ; then 368 echo "Missing the licenses for the following external dependencies:" 369 cat ${MISSING_LICENSES_FILE} 370 fi 371 if [[ -s ${EXTRA_LICENSES_FILE} ]] ; then 372 echo "Please remove the licenses for the following external dependencies:" 373 cat ${EXTRA_LICENSES_FILE} 374 fi 375 rm -rf ${EXTERNAL_DEPENDENCIES_FILE} 376 rm -rf ${LICENSES_FILE} 377 rm -rf ${MISSING_LICENSES_FILE} 378 rm -rf ${EXTRA_LICENSES_FILE} 379 return 1 380 else 381 echo "PASS: all external licenses included." 382 rm -rf ${EXTERNAL_DEPENDENCIES_FILE} 383 rm -rf ${LICENSES_FILE} 384 rm -rf ${MISSING_LICENSES_FILE} 385 rm -rf ${EXTRA_LICENSES_FILE} 386 return 0 387 fi 388 } 389 390 do_pip_package_licenses_check() { 391 echo "Running do_pip_package_licenses_check" 392 echo "" 393 do_external_licenses_check \ 394 "//tensorflow/tools/pip_package:build_pip_package" \ 395 "//tensorflow/tools/pip_package:licenses" 396 } 397 398 do_lib_package_licenses_check() { 399 echo "Running do_lib_package_licenses_check" 400 echo "" 401 do_external_licenses_check \ 402 "//tensorflow:libtensorflow.so" \ 403 "//tensorflow/tools/lib_package:clicenses_generate" 404 } 405 406 do_java_package_licenses_check() { 407 echo "Running do_java_package_licenses_check" 408 echo "" 409 do_external_licenses_check \ 410 "//tensorflow/java:libtensorflow_jni.so" \ 411 "//tensorflow/tools/lib_package:jnilicenses_generate" 412 } 413 414 #Check for the bazel cmd status (First arg is error message) 415 cmd_status(){ 416 if [[ $? != 0 ]]; then 417 echo "" 418 echo "FAIL: ${BUILD_CMD}" 419 echo " $1 See lines above for details." 420 return 1 421 else 422 echo "" 423 echo "PASS: ${BUILD_CMD}" 424 return 0 425 fi 426 } 427 428 # Run bazel build --nobuild to test the validity of the BUILD files 429 # TODO(mikecase): Remove TF Lite exclusion from this list. Exclusion is 430 # necessary since the @androidsdk WORKSPACE dependency is commented 431 # out by default in TF WORKSPACE file. 432 do_bazel_nobuild() { 433 BUILD_TARGET="//tensorflow/..." 434 BUILD_TARGET="${BUILD_TARGET} -//tensorflow/contrib/lite/java/demo/app/src/main/..." 435 BUILD_TARGET="${BUILD_TARGET} -//tensorflow/contrib/lite/schema/..." 436 BUILD_CMD="bazel build --nobuild ${BAZEL_FLAGS} -- ${BUILD_TARGET}" 437 438 ${BUILD_CMD} 439 440 cmd_status \ 441 "This is due to invalid BUILD files." 442 } 443 444 do_pip_smoke_test() { 445 cd "$ROOT_DIR/tensorflow/tools/pip_package" 446 python pip_smoke_test.py 447 } 448 449 do_code_link_check() { 450 tensorflow/tools/ci_build/code_link_check.sh 451 } 452 453 # List .h|.cc files changed in the last non-merge git commit that still exist, 454 # i.e., not removed. 455 # Usage: get_clang_files_to_check [--incremental] 456 get_clang_files_to_check() { 457 if [[ "$1" == "--incremental" ]]; then 458 CHANGED_CLANG_FILES=$(get_changed_files_in_last_non_merge_git_commit | \ 459 grep '.*\.h$\|.*\.cc$') 460 461 # Do not include files removed in the last non-merge commit. 462 CLANG_FILES="" 463 for CLANG_FILE in ${CHANGED_CLANG_FILES}; do 464 if [[ -f "${CLANG_FILE}" ]]; then 465 CLANG_FILES="${CLANG_FILES} ${CLANG_FILE}" 466 fi 467 done 468 469 echo "${CLANG_FILES}" 470 else 471 find tensorflow -name '*.h' -o -name '*.cc' 472 fi 473 } 474 475 do_clang_format_check() { 476 if [[ $# != "0" ]] && [[ $# != "1" ]]; then 477 echo "Invalid syntax when invoking do_clang_format_check" 478 echo "Usage: do_clang_format_check [--incremental]" 479 return 1 480 fi 481 482 if [[ "$1" == "--incremental" ]]; then 483 CLANG_SRC_FILES=$(get_clang_files_to_check --incremental) 484 485 if [[ -z "${CLANG_SRC_FILES}" ]]; then 486 echo "do_clang_format_check will NOT run due to --incremental flag and "\ 487 "due to the absence of .h or .cc code changes in the last commit." 488 return 0 489 fi 490 elif [[ -z "$1" ]]; then 491 # TODO (yongtang): Always pass --incremental until all files have 492 # been sanitized gradually. Then this --incremental could be removed. 493 CLANG_SRC_FILES=$(get_clang_files_to_check --incremental) 494 else 495 echo "Invalid syntax for invoking do_clang_format_check" 496 echo "Usage: do_clang_format_check [--incremental]" 497 return 1 498 fi 499 500 CLANG_FORMAT=${CLANG_FORMAT:-clang-format-3.8} 501 502 success=1 503 for filename in $CLANG_SRC_FILES; do 504 $CLANG_FORMAT --style=google $filename | diff $filename - > /dev/null 505 if [ ! $? -eq 0 ]; then 506 success=0 507 echo File $filename is not properly formatted with "clang-format "\ 508 "--style=google" 509 fi 510 done 511 512 if [ $success == 0 ]; then 513 echo Clang format check fails. 514 exit 1 515 fi 516 echo Clang format check success. 517 } 518 519 do_check_load_py_test() { 520 cd "$ROOT_DIR/tensorflow/tools/pip_package" 521 python check_load_py_test.py 522 } 523 524 do_cmake_python_sanity() { 525 cd "$ROOT_DIR/tensorflow/contrib/cmake" 526 python -m unittest -v python_sanity_test 527 } 528 529 do_check_futures_test() { 530 cd "$ROOT_DIR/tensorflow/tools/test" 531 python check_futures_test.py 532 } 533 534 do_check_file_name_test() { 535 cd "$ROOT_DIR/tensorflow/tools/test" 536 python file_name_test.py 537 } 538 539 # Supply all sanity step commands and descriptions 540 SANITY_STEPS=("do_pylint PYTHON2" "do_pylint PYTHON3" "do_check_futures_test" "do_buildifier" "do_bazel_nobuild" "do_pip_package_licenses_check" "do_lib_package_licenses_check" "do_java_package_licenses_check" "do_pip_smoke_test" "do_check_load_py_test" "do_code_link_check" "do_cmake_python_sanity" "do_check_file_name_test") 541 SANITY_STEPS_DESC=("Python 2 pylint" "Python 3 pylint" "Check that python files have certain __future__ imports" "buildifier check" "bazel nobuild" "pip: license check for external dependencies" "C library: license check for external dependencies" "Java Native Library: license check for external dependencies" "Pip Smoke Test: Checking py_test dependencies exist in pip package" "Check load py_test: Check that BUILD files with py_test target properly load py_test" "Code Link Check: Check there are no broken links" "Test entries in /tensorflow/contrib/cmake/python_{modules|protos|protos_cc}.txt for validity and consistency" "Check file names for cases") 542 543 INCREMENTAL_FLAG="" 544 DEFAULT_BAZEL_CONFIGS="--config=hdfs --config=gcp" 545 546 # Parse command-line arguments 547 BAZEL_FLAGS=${DEFAULT_BAZEL_CONFIGS} 548 for arg in "$@"; do 549 if [[ "${arg}" == "--pep8" ]]; then 550 # Only run pep8 test if "--pep8" option supplied 551 SANITY_STEPS=("do_pep8") 552 SANITY_STEPS_DESC=("pep8 test") 553 elif [[ "${arg}" == "--incremental" ]]; then 554 INCREMENTAL_FLAG="--incremental" 555 else 556 BAZEL_FLAGS="${BAZEL_FLAGS} ${arg}" 557 fi 558 done 559 560 561 FAIL_COUNTER=0 562 PASS_COUNTER=0 563 STEP_EXIT_CODES=() 564 565 # Execute all the sanity build steps 566 COUNTER=0 567 while [[ ${COUNTER} -lt "${#SANITY_STEPS[@]}" ]]; do 568 INDEX=COUNTER 569 ((INDEX++)) 570 571 echo "" 572 echo "=== Sanity check step ${INDEX} of ${#SANITY_STEPS[@]}: "\ 573 "${SANITY_STEPS[COUNTER]} (${SANITY_STEPS_DESC[COUNTER]}) ===" 574 echo "" 575 576 # subshell: don't leak variables or changes of working directory 577 ( 578 ${SANITY_STEPS[COUNTER]} ${INCREMENTAL_FLAG} 579 ) 580 RESULT=$? 581 582 if [[ ${RESULT} != "0" ]]; then 583 ((FAIL_COUNTER++)) 584 else 585 ((PASS_COUNTER++)) 586 fi 587 588 STEP_EXIT_CODES+=(${RESULT}) 589 590 echo "" 591 ((COUNTER++)) 592 done 593 594 # Print summary of build results 595 COUNTER=0 596 echo "==== Summary of sanity check results ====" 597 while [[ ${COUNTER} -lt "${#SANITY_STEPS[@]}" ]]; do 598 INDEX=COUNTER 599 ((INDEX++)) 600 601 echo "${INDEX}. ${SANITY_STEPS[COUNTER]}: ${SANITY_STEPS_DESC[COUNTER]}" 602 if [[ ${STEP_EXIT_CODES[COUNTER]} == "0" ]]; then 603 printf " ${COLOR_GREEN}PASS${COLOR_NC}\n" 604 else 605 printf " ${COLOR_RED}FAIL${COLOR_NC}\n" 606 fi 607 608 ((COUNTER++)) 609 done 610 611 echo 612 echo "${FAIL_COUNTER} failed; ${PASS_COUNTER} passed." 613 614 echo 615 if [[ ${FAIL_COUNTER} == "0" ]]; then 616 printf "Sanity checks ${COLOR_GREEN}PASSED${COLOR_NC}\n" 617 else 618 printf "Sanity checks ${COLOR_RED}FAILED${COLOR_NC}\n" 619 exit 1 620 fi 621