#!/usr/bin/env bash
#
# Build every fuzzer; run them on the testing corpus, and then run them
# in a loop.

set -euo pipefail

usage() {
    echo "Usage: $0 [-d <minutes>] [-n] [-c] [-u] [-h]"
    echo "  -d <minutes>: Switch fuzzers every <minutes>."
    echo "  -h          : Display this message and exit."
    echo "  -n          : Stop after building fuzzers and testing corpus."
    echo "  -c          : Check fuzzers ('cargo fuzz check') and exit."
    echo "  -u          : Update Cargo.lock files in fuzz directories."
}

CHECK_FUZZERS=no
RUN_FUZZERS=yes
DURATION=20
UPDATE=no

while getopts "d:hncu" opt; do
    case "$opt" in
	h)
	    usage
	    exit 0
	    ;;
	n)
	    RUN_FUZZERS=no
	    ;;
	c)
	    CHECK_FUZZERS=yes
	    ;;
	d)
	    DURATION="$OPTARG"
	    ;;
	u)
	    UPDATE=yes
	    ;;
	*)
	    usage
	    exit 1;
	    ;;
    esac
done

echo "Using toolchain +${RUST_FUZZ_TOOLCHAIN:=nightly}. (Override with \$RUST_FUZZ_TOOLCHAIN)"

# Validate that "+${RUST_FUZZ_TOOLCHAIN}" is installed.  This will log a message to stderr
# if it isn't.
cargo "+${RUST_FUZZ_TOOLCHAIN}" -h >/dev/null

# Validate that "cargo fuzz" is installed.
cargo "+${RUST_FUZZ_TOOLCHAIN}" fuzz --help>/dev/null


# Chdir to the source root directory, and make sure we have the corpora checked out.
cd "$(dirname "$0")/.."

if ! test -d "./arti-corpora"; then
    echo "Did not find 'arti-corpora' directory in $(pwd). Cannot proceed." 1>&2
    exit 1
fi



# CHECK ONLY: If requested, check every fuzzer and exit.
if test "$CHECK_FUZZERS" = "yes"; then
    for d in ./crates/*/fuzz; do
	pushd "$(dirname "$d")"
	if test "$UPDATE" = yes; then
	    cargo update
	fi
	for fuzzer in $(cargo fuzz list); do
	    cargo "+${RUST_FUZZ_TOOLCHAIN}" fuzz check "$fuzzer"
	done
	popd
    done
    exit 0
fi

# STEP 1: Build every fuzzer.
for d in ./crates/*/fuzz; do
    pushd "$(dirname "$d")"
    if test "$UPDATE" = yes; then
	cargo update
    fi
    for fuzzer in $(cargo fuzz list); do

	# TODO: Should we do a cargo update? for the fuzzer's cargo.lock?
	cargo "+${RUST_FUZZ_TOOLCHAIN}" fuzz build "$fuzzer"
    done
    popd
done

# STEP 2: Run static test cases

for d in ./crates/*/fuzz; do
    pushd "$(dirname "$d")"
    for fuzzer in $(cargo fuzz list); do
	echo "Running fuzzer '$fuzzer' on static testcases"
	# "-runs=0" means that we won't actually do any additional fuzzing
	# after we load the corpus.
	cargo "+${RUST_FUZZ_TOOLCHAIN}" fuzz run "$fuzzer" -- \
	      -runs=0
    done
    popd
done

if test "$RUN_FUZZERS" = "no"; then
    exit 0
fi

# STEP 3: Run every fuzzer in a loop, for a while, then switch to the next one.

#JOBS=4
#SEED=0

while true; do
    for d in ./crates/*/fuzz; do
	pushd "$(dirname "$d")"
	for fuzzer in $(cargo fuzz list); do
	    cargo "+${RUST_FUZZ_TOOLCHAIN}" fuzz run "$fuzzer" -- \
		-jobs="${JOBS:-0}" \
		-workers="${JOBS:-0}" \
		-max_total_time=$((DURATION * 60)) \
		-seed="${SEED:-0}"
	done
	popd
    done
done
