fuzzing_ci.sh 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. #!/usr/bin/env bash
  2. ##
  3. # Copyright 2018 Google
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. # Run fuzz testing using xcodebuild. Given a total fuzzing duration, select
  17. # a fuzzing target to run for half of the fuzzing duration. The remaining
  18. # duration is split equally over the rest of the fuzzing targets. Must be run
  19. # from the project root directory. The script is intended to execute on travis
  20. # cron jobs, but can run locally (on a macOS) from the project root.
  21. # Total time allowed for fuzzing in seconds (25 minutes for now).
  22. readonly ALLOWED_TIME=1500
  23. # An invalid target that is used to retrieve the list of available targets in
  24. # the returned error message.
  25. readonly INVALID_TARGET="INVALID_TARGET"
  26. # The NONE target that does not perform fuzzing. It is valid but useless.
  27. readonly NONE_TARGET="NONE"
  28. # Script exit codes.
  29. readonly EXIT_SUCCESS=0
  30. readonly EXIT_FAILURE=1
  31. # Get day of the year to use it when selecting a main target.
  32. readonly DOY="$(date +%j)"
  33. # How many last lines of the log to output for a failing job. This is to avoid
  34. # exceeding Travis log size limit (4 Mb).
  35. readonly LINES_TO_OUTPUT=500
  36. # Run fuzz testing only if executing on xcode >= 9 because fuzzing requires
  37. # Clang that supports -fsanitize-coverage=trace-pc-guard.
  38. xcode_version="$(xcodebuild -version | head -n 1)"
  39. xcode_version="${xcode_version/Xcode /}"
  40. xcode_major="${xcode_version/.*/}"
  41. if [[ "${xcode_major}" -lt 9 ]]; then
  42. echo "Unsupported version of xcode."
  43. exit ${EXIT_SUCCESS}
  44. fi
  45. # Helper to run fuzz tests using xcodebuild. Takes the fuzzing target as the
  46. # first argument and the fuzzing duration as the second argument.
  47. function run_xcode_fuzzing() {
  48. xcodebuild \
  49. -workspace 'Firestore/Example/Firestore.xcworkspace' \
  50. -scheme 'Firestore_FuzzTests_iOS' \
  51. -sdk 'iphonesimulator' \
  52. -destination 'platform=iOS Simulator,name=iPhone 7' \
  53. FUZZING_TARGET="$1" \
  54. FUZZING_DURATION="$2" \
  55. test
  56. }
  57. # Run fuzz testing with an INVALID_TARGET and grep the response message line
  58. # that contains the string 'Available targets: {'.
  59. fuzzing_targets_line="$(run_xcode_fuzzing ${INVALID_TARGET} "0" \
  60. | grep "Available targets: {")"
  61. # The fuzzing_targets_line string contains { TARGET1 TARGET2 ... TARGETN }.
  62. # The following command extracts the targets and splits them into an array
  63. # in the variable all_fuzzing_targets.
  64. read -r -a all_fuzzing_targets <<< "$(echo ${fuzzing_targets_line} \
  65. | cut -d "{" -f2 | cut -d "}" -f1)"
  66. # Remove the NONE target, if it exists.
  67. for i in "${!all_fuzzing_targets[@]}"; do
  68. if [[ "${all_fuzzing_targets[${i}]}" == ${NONE_TARGET} ]]; then
  69. unset all_fuzzing_targets[${i}]
  70. fi
  71. done
  72. # Count all targets.
  73. readonly fuzzing_targets_count="${#all_fuzzing_targets[@]}"
  74. # Select a primary target to fuzz test for longer. Changes daily. The variable
  75. # todays_primary_target contains the index of the target selected as primary.
  76. todays_primary_target="$(( ${DOY} % ${fuzzing_targets_count} ))"
  77. # Spend half of allowed time on the selected primary target and half on the
  78. # remaining targets.
  79. primary_target_time="$(( ${ALLOWED_TIME} / 2 ))"
  80. other_targets_time="$(( ${ALLOWED_TIME} / 2 / $(( ${fuzzing_targets_count} - 1)) ))"
  81. script_return=${EXIT_SUCCESS}
  82. for i in "${!all_fuzzing_targets[@]}"; do
  83. fuzzing_duration="${other_targets_time}"
  84. if [[ "${i}" -eq "${todays_primary_target}" ]]; then
  85. fuzzing_duration="${primary_target_time}"
  86. fi
  87. fuzzing_target="${all_fuzzing_targets[${i}]}"
  88. echo "Running fuzzing target ${fuzzing_target} for ${fuzzing_duration} seconds..."
  89. fuzzing_results="$(run_xcode_fuzzing "${fuzzing_target}" "${fuzzing_duration}")"
  90. # Get the last line of the fuzzing results.
  91. fuzzing_results_last_line="$(echo "${fuzzing_results}" | tail -n1)"
  92. # If fuzzing succeeded, the last line will start with "Done"; otherwise,
  93. # print all fuzzing results.
  94. if [[ "${fuzzing_results_last_line}" == Done* ]]; then
  95. echo "Fuzz testing for target ${fuzzing_target} succeeded. Ignore the ** TEST FAILED ** message."
  96. else
  97. echo "Error: Fuzz testing for target ${fuzzing_target} failed."
  98. script_return=${EXIT_FAILURE}
  99. echo "Fuzzing logs:"
  100. echo "${fuzzing_results}" | tail -n${LINES_TO_OUTPUT}
  101. echo "End fuzzing logs"
  102. fi
  103. done
  104. exit ${script_return}