| #!/bin/bash |
| # Copyright 2020 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| #### CATEGORY=Build |
| ### Locally replicate the result of infra builds and tests |
| ## |
| ## This command attempts to replicate the result of an infra builder by reading |
| ## build information and translating to corresponding local 'fx' commands. |
| ## |
| ## usage: fx repro [-h|--help] <BUILD_ID>|<BUILD_URL>|<FINT_PARAMS_FILE> |
| ## |
| ## BUILD_ID: the id of a build, prefixed or not by "b". For example: |
| ## b8860637037546448224 or 8860637037546448224 |
| ## |
| ## BUILD_URL: the URL you are redirected to when you click on a box in the |
| ## "Tryjob" section of a Gerrit CL. For example: |
| ## https://ci.chromium.org/p/fuchsia/builders/try/fuchsia-x64-release/b8860637037546448224? |
| ## |
| ## FINT_PARAMS_FILE: a local path to a fint params file, usually a *.textproto file |
| ## in //integration/infra/config/generated/*/fint_params/. For example: |
| ## integration/infra/config/generated/fuchsia/fint_params/try/core.x64-release.textproto |
| ## |
| ## Please provide feedback on go/fx-repro-feedback |
| |
| source "${FUCHSIA_DIR}/tools/devshell/lib/style.sh" || exit $? |
| source "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/lib/vars.sh || exit $? |
| |
| set -e -o pipefail |
| |
| function repro { |
| local build_id="$1" |
| local fint_params_file_relative="$2" |
| |
| local can_sync_to=false |
| |
| local build_dir_name |
| if [[ -n "$build_id" ]]; then |
| can_sync_to=true |
| build_dir_name="out/repro_${build_id}" |
| |
| check_bb_auth |
| build=$(get_build "$build_id") |
| if [[ -z "$build" ]]; then |
| fx-error "Could not retrieve build from buildbucket." |
| return 1 |
| fi |
| |
| project="$(get_builder_field project)" |
| bucket="$(get_builder_field bucket)" |
| builder="$(get_builder_field builder)" |
| |
| local build_url="https://ci.chromium.org/p/${project}/builders/${bucket}/${builder}/b${build_id}" |
| |
| fint_params_file_relative="$(get_fint_params_path)" |
| if [[ -z "$fint_params_file_relative" ]]; then |
| fx-error "This build did not emit a 'fint_params_path' property." |
| fx-error "If the build ran against an old revision, you may need to run" |
| fx-error |
| fx-error " fx sync-to b${build_id}" |
| fx-error |
| fx-error "to switch to that version of the checkout and then run 'fx repro' again." |
| return 1 |
| fi |
| |
| local fint_params_file="${FUCHSIA_DIR}/${fint_params_file_relative}" |
| if [[ ! -f "${fint_params_file}" ]]; then |
| # The fint params file used by the build doesn't exist; this can be for |
| # one of several reasons. |
| if [[ "${fint_params_file_relative}" =~ ^integration/infra/config/generated/[a-z]+/specs/.+\.fint\.textproto$ ]]; then |
| if [[ -n "$build_id" ]]; then |
| fx-error "This build uses an old fint parameters file that's no longer available at HEAD." |
| fx-error "Try switching back to the revision used by the build by running:" |
| fx-error |
| fx-error " fx sync-to b${build_id}" |
| fx-error |
| fx-error "and then run 'fx repro' again." |
| else |
| fx-error "This is an old fint parameter file that's no longer available at HEAD." |
| fi |
| else |
| fx-error "Cannot find fint parameters for this build: ${fint_params_file_relative}" |
| fx-error "If this is an internal builder, make sure your Fuchsia source tree has the internal" |
| fx-error "integration repository as described in https://fuchsia.dev/internal/intree/development/get-the-source-code" |
| fi |
| return 1 |
| fi |
| else |
| if [[ ! -f "${FUCHSIA_DIR}/${fint_params_file_relative}" ]]; then |
| fx-error "Cannot find file ${FUCHSIA_DIR}/${fint_params_file_relative}" |
| usage |
| return 1 |
| fi |
| if [[ "${fint_params_file_relative}" =~ ^integration/infra/config/generated/(.+)/fint_params/(.+)/(.+).textproto$ ]]; then |
| project="${BASH_REMATCH[1]}" |
| bucket="${BASH_REMATCH[2]}" |
| builder="${BASH_REMATCH[3]}" |
| else |
| fx-error "Invalid path format for fint params file: ${fint_params_file_relative}" |
| return 1 |
| fi |
| build_dir_name="out/repro_${project}_${bucket}_${builder}" |
| fi |
| |
| instructions_file="${FUCHSIA_DIR}/${build_dir_name}.instructions" |
| |
| fx-config-read |
| if [[ "${FUCHSIA_BUILD_DIR}" =~ out/repro_ ]]; then |
| fx-error "Build directory is still set to a previous fx repro run: ${FUCHSIA_BUILD_DIR#${FUCHSIA_DIR}/}" |
| fx-error "Please restore it to the previous state." |
| if [[ -f "${FUCHSIA_BUILD_DIR}.instructions" ]]; then |
| fx-error "Instructions in ${FUCHSIA_BUILD_DIR#${FUCHSIA_DIR}/}.instructions" |
| else |
| fx-error "# fx set-build-dir out/default" |
| fi |
| exit 1 |
| fi |
| |
| # Remove old instructions file if one already exists with the same name. |
| rm -f "${instructions_file}" |
| |
| print_comment "Fint params file: //${fint_params_file_relative}" |
| if [[ -n "$build_id" ]]; then |
| print_comment "Build URL: ${build_url}" |
| fi |
| |
| if $can_sync_to; then |
| print_spacer |
| print_comment "Sync the Fuchsia source tree to the same commit:" |
| print_cmd "fx sync-to ${build_id}" |
| fi |
| |
| print_spacer |
| print_comment "Set the build configuration:" |
| print_cmd "fx --dir=${build_dir_name} set --fint-params-path '$fint_params_file_relative'" |
| print_spacer |
| |
| print_comment "Build:" |
| print_cmd "fx build --fint-params-path '$fint_params_file_relative'" |
| print_spacer |
| |
| print_comment "If necessary, start an emulator or connect a device" |
| print_spacer |
| |
| print_comment "Run tests (use the flag '--e2e' if you also want to run E2E tests):" |
| repro_test_commands |
| print_spacer |
| |
| if [[ -n "$build_id" ]]; then |
| repro_failed_test_commands |
| fi |
| |
| if [[ "$(build_has_binary_sizes)" == "true" ]]; then |
| print_comment "Check component sizes:" |
| print_cmd "fx size_checker -build-dir ${build_dir_name} -sizes-json-out ${FUCHSIA_DIR}/${build_dir_name}.sizes.json" |
| print_spacer |
| fi |
| |
| print_comment "Restore to the previous state:" |
| print_cmd "fx set-build-dir ${FUCHSIA_BUILD_DIR#${FUCHSIA_DIR}/}" |
| if $can_sync_to; then |
| print_cmd "fx sync-to reset" |
| fi |
| print_spacer |
| print_comment "(Optional) Remove the repro build directory:" |
| print_cmd "rm -Rf ${FUCHSIA_DIR}/${build_dir_name}" |
| |
| # TODO: add instructions to run botanist, for example: |
| # "Execute the steps in 'Reproducing the task locally' from https://chrome-swarming.appspot.com/task?id=4d23bde27aab0910" |
| # or, to simplify, just print out the botanist command line, e.g.: |
| # ./botanist -level trace run -images gs://fuchsia-artifacts-internal/builds/8875967070288043824/images/images.json -timeout 1800s -syslog syslog.txt -repo http://localhost:8080/fuchsia-artifacts-internal/builds/8875967070288043824/packages/repository -blobs http://localhost:8080/fuchsia-artifacts-internal/blobs -ssh private_key -config ./qemu.json ./testrunner -out-dir out -snapshot-output snapshot.zip -per-test-timeout 300s tests.json] |
| # |
| |
| echo |
| echo "*** These instructions are saved to ${instructions_file#${FUCHSIA_DIR}/} in case you need them later." |
| echo "*** Please, provide feedback on http://go/fx-repro-feedback" |
| } |
| |
| function repro_failed_test_commands { |
| local status |
| status="$(get_build_status)" |
| if [[ "$status" != "FAILURE" ]]; then |
| return |
| fi |
| |
| print_comment "Run only failed tests:" |
| |
| local failed_tests |
| failed_tests="$(get_failed_tests)" |
| if [[ -z "$failed_tests" ]]; then |
| print_comment "Could not find the failed_test_names property in this build." |
| print_comment "Maybe it failed in other stages, e.g. build, or it is an old build. Only recent builds export the names of failed tests as properties." |
| print_comment "If this is an old build, you may find failed tests in $build_url" |
| return |
| fi |
| repro_test_commands "$failed_tests" |
| print_spacer |
| } |
| |
| function repro_test_commands { |
| local failed_tests=("$@") |
| |
| print_cmd "fx test ${failed_tests[*]}" |
| } |
| |
| function jq { |
| jq_command="$1" |
| json_obj="$2" |
| fx-command-run jq -r "$jq_command" <<< "$json_obj" |
| } |
| |
| function get_build_status { |
| jq ".status" "$build" |
| } |
| |
| function get_builder_field { |
| jq ".builder.$1" "$build" |
| } |
| |
| # Returns a space-separated list of all the tests that failed for the build. |
| function get_failed_tests { |
| jq '.output.properties.failed_test_names? // [] | unique | join(" ")' "$build" |
| } |
| |
| # Returns the fint params path used by the build. Older builds may not have this |
| # property set. |
| function get_fint_params_path { |
| jq '.output.properties.fint_params_path? // ""' "$build" |
| } |
| |
| # Returns "true" or "false" depending on whether the build exposes a |
| # "binary_sizes" output property. |
| function build_has_binary_sizes { |
| jq '.output.properties | has("binary_sizes")' "$build" |
| } |
| |
| function usage { |
| fx-command-help |
| } |
| |
| function print { |
| if [[ -n "${instructions_file}" ]]; then |
| echo -e "$*" >> "${instructions_file}" |
| fi |
| echo -e "$*" |
| } |
| |
| function print_cmd { |
| if [[ -n "${instructions_file}" ]]; then |
| echo -e "$*" >> "${instructions_file}" |
| fi |
| style::echo --green "$*" |
| } |
| |
| function print_spacer { |
| print "" |
| } |
| |
| function print_comment { |
| print "# $*" |
| } |
| |
| function check_bb_auth { |
| local bbtool="${FUCHSIA_DIR}/prebuilt/tools/buildbucket/bb" |
| while ! "$bbtool" auth-info >/dev/null 2>&1; do |
| fx-warn "Please login to Buildbucket first:" |
| "$bbtool" auth-login |
| done |
| } |
| |
| function get_build { |
| local build_id="$1" |
| "${FUCHSIA_DIR}/prebuilt/tools/buildbucket/bb" get "$build_id" -json -fields "status,builder,output.properties" |
| } |
| |
| function main { |
| # Expect exactly one state. |
| if [[ $# -ne 1 ]]; then |
| fx-error "BUILD_URL or FINT_PARAMS_FILE missing." |
| usage |
| return 1 |
| fi |
| |
| if [[ "$1" =~ ^(-h|--help)$ ]]; then |
| usage |
| return 0 |
| fi |
| |
| local arg="$1" |
| arg="${arg#http://}" |
| arg="${arg#https://}" |
| |
| local fint_params_file_relative="" |
| local build_id="" |
| if [[ "${arg}" =~ ^b?([0-9]+)[?]?$ ]]; then |
| build_id="${BASH_REMATCH[1]}" |
| elif [[ "${arg}" =~ ^cr-buildbucket.appspot.com/build/b?([0-9]+) ]]; then |
| build_id="${BASH_REMATCH[1]}" |
| elif [[ "${arg}" =~ ^(luci-milo.appspot.com|ci.chromium.org)(/ui)?/p/(.+)/builders/(.+)/(.+)/b([0-9]+) ]]; then |
| build_id="${BASH_REMATCH[6]}" |
| elif [[ "${arg}" == integration/* ]]; then |
| fint_params_file_relative="${arg}" |
| else |
| fx-error "Unsupported build ID, build URL or fint params file: ${arg}" |
| usage |
| return 1 |
| fi |
| |
| repro "${build_id}" "${fint_params_file_relative}" |
| } |
| |
| main "$@" |