angr/corpus_tests/scripts/snapshot_diff.sh
Fish 5fc89c39cf
Multiple fixes for corpus tests. (#5052)
* create snapshot files at a different base dir.

* Fix branch name in "Push snapshots to snapshot repo".

* sanitize snapshot index file path

* snapshot_diff.sh expects GITHUB_TOKEN instead of GH_TOKEN.

* snapsnot_diff.sh needs repo specified.

* Fix PR number in snapshot_diff summary.

* Use ANGR_PR_PAT instead when updating angr PRs.
2024-11-29 21:53:44 -07:00

259 lines
6.8 KiB
Bash
Executable file

#!/usr/bin/env bash
set -o pipefail
help() {
SCRIPT_NAME="$(basename "$0")"
cat <<ANGR
Usage: $SCRIPT_NAME (<remote_path>) | (<local_snapshot1> <local_snapshot2>) [OPTIONS]
Compares 1-to-1 decompilation snapshots for a particular file.
Examples:
$SCRIPT_NAME -H pr-404 -t "\$(gh auth token)"
$SCRIPT_NAME -H pr-404 -p stable/cgc-challenges/ -t "\$(gh auth token)"
Options:
-d, --snapshot-directory
Specify the directory to cache the decompilation snapshots.
-h, --help
Show this help message and exit
-p <path>, --path <path>
The file path of the snapshot to compare.
-v, --verbose
Enable more verbose output.
GitHub Options:
-b <ref>, --base-ref <ref>
The name of the base repository ref to compare snapshots from. This
defaults to HEAD.
-H <ref>, --head-ref <ref>
The name of the head repository ref to compare snapshots to.
-R <repo>, --repo <repo>
The GitHub repository to enumerate using the OWNER/REPO format.
This defaults to project-purcellville/snapshots-0000.
-t <token>, --token <token>
A GitHub token with access permissions. This can also be specified via the
GITHUB_TOKEN environment variable.
ANGR
exit 1
}
declare -a FILEPATH
REF_BASE="HEAD"
REPO="project-purcellville/snapshots-0000"
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
SNAPSHOT_DIR=""
VERBOSE=""
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-b|--base-ref)
REF_BASE="${2}"
shift 2
;;
-d|--snapshot-dir)
SNAPSHOT_DIR="${2}"
shift 2
;;
-h|--help)
help
;;
-H|--head-ref)
REF_HEAD="$2"
shift 2
;;
-p|--path)
FILEPATH+=("$2")
shift 2
;;
-R|--repo)
REPO="$2"
shift 2
;;
-t|--token)
GITHUB_TOKEN="$2"
shift 2
;;
-v|--verbose)
VERBOSE=true
shift
;;
*)
echo "Unknown option: $1"
help
;;
esac
done
}
parse_args "$@"
if [[ -z "${REPO}" ]]; then
echo "ERROR: <repo> not provided"
echo
help
fi
if [[ -z "${REF_HEAD}" ]]; then
echo "ERROR: head <ref> not provided"
echo
help
fi
if [[ -z "${SNAPSHOT_DIR}" ]]; then
trap 'rm -rf "$TEMP_DIR"' EXIT
SNAPSHOT_DIR=$(mktemp -d)
else
mkdir -p "${SNAPSHOT_DIR}"
fi
# create the file path for the index file; sanitize REF_HEAD to replace "/" with "_"
SANITIZED_REF_HEAD=$(echo -n "${REF_HEAD}" | tr '/' '_')
SNAPSHOT_INDEX_FILE="${SNAPSHOT_DIR}/index_${SANITIZED_REF_HEAD}.txt"
# Retrieve a list of all of the available snapshot file paths.
if ! [[ -f "${SNAPSHOT_INDEX_FILE}" ]]; then
printf "Indexing remote: %s\n" "${REF_HEAD}" >&2
"${SCRIPT_DIR}/gh_ls.sh" \
-R "${REPO}" \
--branch "${REF_HEAD}" \
--path snapshots/ \
--token "${GITHUB_TOKEN}" \
>> "${SNAPSHOT_INDEX_FILE}"
fi
# If FILEPATHs were provided, call `gh_ls.sh` and retrieve all available
# valid file paths in case they were file patterns.
if ! [[ "${#FILEPATH[@]}" -eq 0 ]]; then
declare -a FILEPATH_NEW
for index in "${!FILEPATH[@]}"; do
printf "\rHydrating remote path: %s" "${FILEPATH[$index]}" >&2
if [[ "${FILEPATH[$index]}" != snapshots/* ]]; then
FILEPATH[index]="snapshots/${FILEPATH[$index]}"
fi
declare -a FILEPATH_TMP
mapfile -t FILEPATH_TMP < <(
"${SCRIPT_DIR}/gh_ls.sh" \
-R "${REPO}" \
--branch "${REF_HEAD}" \
--path "${FILEPATH[$index]}" \
--token "${GITHUB_TOKEN}"
)
FILEPATH_NEW=( "${FILEPATH_NEW[@]}" "${FILEPATH_TMP[@]}" )
done
FILEPATH=("${FILEPATH_NEW[@]}")
printf "\33[2K\rHydrated %s snapshot paths, done.\n" "${#FILEPATH_NEW[@]}" >&2
fi
# Try and find a PR merging from the `REF_HEAD` to the `REF_BASE` and get a list
# of only the files with changes.
if [[ "${#FILEPATH[@]}" -eq 0 ]]; then
mapfile -t FILEPATH < <(
gh pr list \
-R "${REPO}" \
--json headRefName \
--json files \
--jq ".[] | select(.headRefName == \"$REF_HEAD\")
| .files[].path"
)
printf "Selected changed files from PR: %s\n" "${#FILEPATH[@]}" >&2
fi
# If there are still no file paths, get the list of paths from the head ref.
if [[ "${#FILEPATH[@]}" -eq 0 ]]; then
mapfile -t FILEPATH < <(sort "${SNAPSHOT_INDEX_FILE}")
fi
# Download each snapshot.
# If the FILEPATH is missing a leading `snapshots/`, add it.
# If it is missing a trailing `.json.txt`, add it.
N="${N:-$(($(getconf _NPROCESSORS_ONLN) * 2))}"
for index in "${!FILEPATH[@]}"; do
printf "\33[2K\rDownloading snapshots (%s/%s)" "$((index + 1))" "${#FILEPATH[@]}" >&2
if [[ "${FILEPATH[$index]}" != snapshots/* ]]; then
FILEPATH[index]="snapshots/${FILEPATH[$index]}"
fi
if [[ "${FILEPATH[$index]}" != *.json.txt ]]; then
FILEPATH[index]="${FILEPATH[$index]}.json.txt"
fi
# Concurrency control. Let up to ${N} jobs run in the background.
mapfile -t pids < <(jobs -pr)
[[ ${#pids[@]} -ge ${N} ]] && wait -n
unset pids
DIRPATH="${SNAPSHOT_DIR}/${REF_BASE}/$(dirname "${FILEPATH[$index]}")"
FILENAME="$(basename "${FILEPATH[$index]}")"
mkdir -p "${DIRPATH}"
SNAPSHOT_BASE_URL="https://raw.githubusercontent.com/${REPO}/${REF_BASE}/${FILEPATH[$index]}"
if ! [[ -f "${DIRPATH}/${FILENAME}" ]]; then
curl \
--location \
--header "Authorization: token $GITHUB_TOKEN" \
--output "${DIRPATH}/${FILENAME}" \
--silent \
"${SNAPSHOT_BASE_URL}" &
fi
DIRPATH="${SNAPSHOT_DIR}/${REF_HEAD}/$(dirname "${FILEPATH[$index]}")"
FILENAME="$(basename "${FILEPATH[$index]}")"
mkdir -p "${DIRPATH}"
SNAPSHOT_URL="https://raw.githubusercontent.com/${REPO}/${REF_HEAD}/${FILEPATH[$index]}"
if ! [[ -f "${DIRPATH}/${FILENAME}" ]]; then
curl \
--location \
--header "Authorization: token $GITHUB_TOKEN" \
--output "${DIRPATH}/${FILENAME}" \
--silent \
"${SNAPSHOT_URL}" &
fi
done
wait
echo ", done." >&2
# XXX: This was indulgent. Revisit performance here.
{
for index in "${!FILEPATH[@]}"; do
SNAPSHOT1="${SNAPSHOT_DIR}/${REF_BASE}/${FILEPATH[$index]}"
SNAPSHOT2="${SNAPSHOT_DIR}/${REF_HEAD}/${FILEPATH[$index]}"
if [[ -n "${VERBOSE}" ]]; then
"${SCRIPT_DIR}/classify_diff.sh" -v "${SNAPSHOT1}" "${SNAPSHOT2}"
else
"${SCRIPT_DIR}/classify_diff.sh" "${SNAPSHOT1}" "${SNAPSHOT2}"
fi
done
} | \
jq 'select(.identical == false)' | \
jq -s 'reduce(.[].changes |
to_entries[] |
select(.value != 0)) as {"key": $key, "value": $value}
({}; .["changes"][$key]["hunks"] += $value |
.["changes"][$key]["files"] += 1)' | \
jq -r '.changes |
to_entries |
["-----------,-----,-----"] +
map("\(.key | gsub("_"; " ") | ascii_upcase),\(.value.hunks),\(.value.files)") |
.[]' | \
column -t -N "Change Type,Hunks,Files" -o " | " -s ","