#!/usr/bin/env bash

# published under GNU General Public License, version 3 (GPL-3.0)
# authors Hans Eirik Bull and Stefan Rueger, 2024

progname=$(basename "$0")
tfiles=$(dirname "$0")/test_files
tfiles=$(printf "%q" "$tfiles") # Quote directory string in case there are spaces etc

avrdude_conf=''                 # Configuration for every run, eg, '-C path_to_avrdude_conf'
delay=0.75                      # Some programmers need a delay between AVRDUDE calls
avrdude_bin=avrdude             # Executable
list_only=0                     # Normal run
declare -a pgm_and_target=()    # Array with test option strings, eg, "-c dryrun -p m328p"
skip_eeprom=0                   # Do not skip EEPROM tests for bootloaders by default
tmp=/dev/shm                    # Temporary RAM directory
addtests=0                      # Add dryrun/dryboot test cases?
verbose=0                       # Do not show AVRDUDE errors and warnings by default

Usage() {
cat <<END
Syntax: $progname {<opts>}
Function: test AVRDUDE for certain programmer and part combinations
Options:
    -c <configuration spec>     additional configuration options used for all runs
    -d <sec>                    delay between test commands (default $delay seconds)
    -e <avrdude path>           set path of AVRDUDE executable (default $avrdude_bin)
    -l                          list test commands but do not execute them
    -p <programmer/part specs>  can be used multiple times, overrides default tests
    -s                          skip EEPROM tests for bootloaders
    -t <dir>                    temporary directory (default $tmp)
    -T                          Add dryrun/dryboot test cases to test $progname
    -v                          verbose: show AVRDUDE error and warning messages
    -? or -h                    show this help text
Note: some Windows environments require the option -t . or similar
Examples:
    \$ $progname -s -p "-c dryboot -p t13" -p "-c dryrun -p m4809"
    \$ $progname -d 2 -p "-cjtag2updi -patmega4809 -Pusb:2341:0058 -r"
END
}

while getopts ":\?hc:d:e:lp:st:Tv" opt; do
  case ${opt} in
    c) avrdude_conf="$OPTARG"
        ;;
    d) delay="$OPTARG"
        ;;
    e) avrdude_bin="$OPTARG"
        ;;
    l) list_only=1
        ;;
    p) pgm_and_target+=("$OPTARG")
        ;;
    s) skip_eeprom=1
        ;;
    t) tmp="$OPTARG"
        ;;
    T) addtests=1
        ;;
    v) verbose=1
        ;;
    --) shift;
        break
        ;;
   [h?])
       Usage; exit 0
        ;;
   \?) echo "Invalid option: -$OPTARG" 1>&2
       Usage; exit 1
        ;;
   : ) echo "Invalid option: -$OPTARG requires an argument" 1>&2
       Usage; exit 1
       ;;
  esac
done
shift $((OPTIND -1))

if [[ $addtests -eq 1 ]]; then
  pgm_and_target+=(
    "-c dryrun  -p at89s51"       "-c dryrun  -p at89s52"       "-c dryboot -p at90can32"
    "-c dryrun  -p at90can32"     "-c dryboot -p at90s1200"     "-c dryboot -p at90s2313"
    "-c dryrun  -p at90s2313"     "-c dryboot -p at90s4414"     "-c dryrun  -p at90s4414"
    "-c dryboot -p at90s8515"     "-c dryrun  -p at90s8515"     "-c dryboot -p atmega103"
    "-c dryrun  -p atmega103"     "-c dryboot -p atmega128rfr2" "-c dryrun  -p atmega128rfr2"
    "-c dryboot -p atmega163"     "-c dryrun  -p atmega163"     "-c dryboot -p atmega16"
    "-c dryrun  -p atmega16"      "-c dryboot -p atmega16hva"   "-c dryrun  -p atmega16hva"
    "-c dryboot -p atmega2560"    "-c dryrun  -p atmega2560"    "-c dryboot -p atmega256rfr2"
    "-c dryrun  -p atmega256rfr2" "-c dryboot -p atmega406"     "-c dryrun  -p atmega406"
    "-c dryboot -p atmega4808"    "-c dryrun  -p atmega4808"    "-c dryboot -p atmega640"
    "-c dryrun  -p atmega640"     "-c dryboot -p atmega64"      "-c dryrun  -p atmega64"
    "-c dryboot -p atmega64hve2"  "-c dryrun  -p atmega64hve2"  "-c dryboot -p atmega64rfr2"
    "-c dryrun  -p atmega64rfr2"  "-c dryboot -p atmega808"     "-c dryrun  -p atmega808"
    "-c dryboot -p atmega8"       "-c dryrun  -p atmega8"       "-c dryrun  -p attiny11"
    "-c dryboot -p attiny13"      "-c dryrun  -p attiny13"      "-c dryboot -p attiny1604"
    "-c dryrun  -p attiny1604"    "-c dryboot -p attiny202"     "-c dryrun  -p attiny202"
    "-c dryrun  -p attiny20"      "-c dryboot -p attiny261"     "-c dryrun  -p attiny26"
    "-c dryrun  -p attiny28"      "-c dryboot -p attiny3216"    "-c dryrun  -p attiny3216"
    "-c dryboot -p attiny3224"    "-c dryrun  -p attiny3224"    "-c dryboot -p attiny402"
    "-c dryrun  -p attiny402"     "-c dryrun  -p attiny40"      "-c dryboot -p attiny461"
    "-c dryrun  -p attiny461"     "-c dryboot -p attiny48"      "-c dryrun  -p attiny48"
    "-c dryrun  -p attiny4"       "-c dryboot -p attiny804"     "-c dryrun  -p attiny804"
    "-c dryboot -p attiny828"     "-c dryrun  -p attiny828"     "-c dryboot -p attiny88"
    "-c dryrun  -p attiny88"      "-c dryrun  -p attiny9"       "-c dryboot -p atxmega128a4"
    "-c dryrun  -p atxmega128a4"  "-c dryboot -p atxmega128a4u" "-c dryrun  -p atxmega128a4u"
    "-c dryboot -p atxmega128c3"  "-c dryrun  -p atxmega128c3"  "-c dryboot -p atxmega16a4u"
    "-c dryrun  -p atxmega16a4u"  "-c dryboot -p atxmega16e5"   "-c dryrun  -p atxmega16e5"
    "-c dryboot -p atxmega192a1"  "-c dryrun  -p atxmega192a1"  "-c dryboot -p atxmega192c3"
    "-c dryrun  -p atxmega192c3"  "-c dryboot -p atxmega256a1"  "-c dryrun  -p atxmega256a1"
    "-c dryboot -p atxmega256c3"  "-c dryrun  -p atxmega256c3"  "-c dryboot -p atxmega32a4u"
    "-c dryrun  -p atxmega32a4u"  "-c dryboot -p atxmega32e5"   "-c dryrun  -p atxmega32e5"
    "-c dryboot -p atxmega384c3"  "-c dryrun  -p atxmega384c3"  "-c dryboot -p atxmega64a4"
    "-c dryrun  -p atxmega64a4"   "-c dryboot -p atxmega64a4u"  "-c dryrun  -p atxmega64a4u"
    "-c dryboot -p atxmega8e5"    "-c dryrun  -p atxmega8e5"    "-c dryboot -p avr128da28"
    "-c dryrun  -p avr128da28"    "-c dryboot -p avr16ea28"     "-c dryrun  -p avr16ea28"
    "-c dryboot -p avr32da28"     "-c dryrun  -p avr32da28"     "-c dryboot -p avr32ea28"
    "-c dryrun  -p avr32ea28"     "-c dryboot -p avr64da28"     "-c dryrun  -p avr64da28"
    "-c dryboot -p avr64dd14"     "-c dryrun  -p avr64dd14"     "-c dryboot -p avr64du28"
    "-c dryrun  -p avr64du28"     "-c dryboot -p avr64ea28"     "-c dryrun  -p avr64ea28"
    "-c dryboot -p avr8ea28"      "-c dryrun  -p avr8ea28"      "-c dryboot -p lgt8f328p"
    "-c dryrun  -p lgt8f328p"
  )
fi

if [[ ${#pgm_and_target[@]} -eq 0 ]]; then
  # Default tests in absence of -p
  pgm_and_target+=(
    "-cpkobn_updi -B1 -patmega3208"
    "-cpkobn_updi -B1 -patmega3209"
    "-cpkobn_updi -B1 -patmega4808"
    "-cjtag2updi -patmega4809 -Pusb:2341:0058 -r"
    "-cpkobn_updi -B1 -pattiny3217"
    "-cpkobn_updi -B1 -pavr128da48"
    "-cpkobn_updi -B1 -pavr128db48"
    "-cpkobn_updi -B1 -pavr64dd32"
    "-cpkobn_updi -B1 -pavr64ea48"
    "-cpkobn_updi -B1 -pavr16eb32"
    "-cxplainedmini_isp -patmega328pb"
    "-cxplainedmini_updi -pattiny1616"
    "-cxplainedmini_updi -pattiny3217"
    "-cxplainedpro_updi -B1 -pattiny817"
    "-cxplainedpro_pdi -B0.5 -patxmega128a1u"
    "-cxplainedpro -B4MHz -patmega256rfr2"
  )
fi

arraylength=${#pgm_and_target[@]}
type "$avrdude_bin" >/dev/null 2>&1 || { echo "$progname: cannot execute $avrdude_bin"; exit 1; }
echo -n "Testing $avrdude_bin"
$avrdude_bin -v 2>&1 | grep Version | cut -f2- -d: | sed s/Version/version/

[[ -d $tmp && -w $tmp ]] || tmp=/tmp # Fall back to /tmp if tmp directory unusable
status=$(mktemp "$tmp/$progname.status.XXXXXX")
logfile=$(mktemp "$tmp/$progname.log.XXXXXX")
outfile=$(mktemp "$tmp/$progname.out.XXXXXX")
tmpfile=$(mktemp "$tmp/$progname.tmp.XXXXXX")
resfile=$(mktemp "$tmp/$progname.res.XXXXXX")
trap "rm -f $status $logfile $outfile $tmpfile $resfile" EXIT

TIMEFORMAT=%R                   # time built-in only returns elapsed wall-clock time
elapsed=-1                      # Global variable holding time of last execute command in seconds
command=(sleep 0.1)             # Array with AVRDUDE command
emulated=0                      # Is programmer dryrun or dryboot, ie, programming is emulated?

# Execute args as command, set $elapsed and return exit value of command; don't call in a subshell
execute () {
  if [[ $list_only -eq 1 ]]; then
    echo "\$ ${command[@]}" | sed "s/ -l [^ ]* / /" | tr -s " "
    return 0;
  fi
  [[ $emulated -eq 0 && $elapsed != -1 ]] && sleep "$delay"
  # These shenanigans keep stdout, stderr and the exit code of the command
  { read elapsed < <({ time { eval "$@"; echo $? >"$status"; } 2>&4 4>&-; } 4>&2 2>&1 >&3 3>&-); } 3>&1
  return $(cat "$status")
}

# Evaluate condition, print execution time and success or failure
result () {
  local ret;

  eval "$@" && ret=0 || ret=1
  if [[ $list_only -eq 0 ]]; then
    if [[ $ret -eq 0 ]]; then
      echo ✅ "$(printf '%7.3f s' $elapsed): $specify"
    else
      echo ❌ "$(printf '%7.3f s' $elapsed): $specify (failed command below)"
      echo "\$ ${command[@]}" | sed "s/ -l [^ ]* / /" | tr -s " "
      FAIL=true
      exitstate=1
      [[ $emulated -eq 0 ]] && sleep 4 # Let the hw settle down before next test
    fi
    [[ $verbose -eq 1 ]] && { cat $logfile $outfile; }
  else
    cat $outfile
  fi
  cp /dev/null $outfile; cp /dev/null $logfile; elapsed=-999; specify="unknown"; command=(sleep 0.1)
  return $ret
}

nofusetest=(-pattiny11/ -pt11/ -pattiny12/ -pt12/ -pattiny15/ -pt15/ -pat89s51/ -p89s51/
  -pat89s52/ -p89s52/ -pat90s1200/ -p1200/ -pat90s4414/ -p4414/ -pat90s2313/ -p2313/ -pat90s2333/
  -p2333/ -pat90s2343/ -p2343/ -pat90s2323/ -p2323/ -pattiny22/ -pt22/ -pat90s4433/ -p4433/
  -pat90s8515/ -p8515/ -pat90s8535/ -p8535/ -pat90s4434/ -p4434/ -patmega163/ -pm163/
  -patmega161/ -pm161/ -pattiny28/ -pt28/ -patxmega64a4/ -px64a4/ -patxmega128a4/ -px128a4/
  -patxmega192a1/ -px192a1/ -patxmega256a1/ -px256a1/ -pat32uc3a0512/ -puc3a0512/ -pavr8ea28/
  -pavr8ea28/ -pavr8ea32/ -pavr8ea32/ -plgt8f88p/ -plgt8f88p/ -plgt8f168p/ -plgt8f168p/
  -plgt8f328p/ -plgt8f328p/)

exitstate=0
for (( p=0; p<$arraylength; p++ )); do
  # Isolate programmer and part (assumes -c prog or -cprog but not sth more tricky such as -qc prog)
  programmer=$(echo ${pgm_and_target[$p]} | sed 's/  *\([^-]\)/\1/g' | tr \  \\n | grep ^-c | tr A-Z a-z)
  part=$(echo ${pgm_and_target[$p]} | sed 's/  *\([^-]\)/\1/g' | tr \  \\n | grep ^-p | tr A-Z a-z)
  emulated=0
  [[ "$programmer" == -cdryrun || "$programmer" == -cdryboot ]] && emulated=1

  if [[ $list_only -eq 1 ]]; then
    [[ p -ne 0 ]] && echo
    echo "# ${pgm_and_target[$p]}"
    key=''
  else
    echo "Prepare \"${pgm_and_target[$p]}\" and press 'enter' or 'space' to continue. Press any other key to skip"
    read -n1 -s -r -p $'' key
    [[ $emulated -eq 0 ]] && sleep 0.15 # Debounce key when not using dryrun/dryboot
  fi

  if [ "$key" == '' ]; then
    FAIL=false
    avrdude=($avrdude_bin -l $logfile $avrdude_conf -qq ${pgm_and_target[$p]})

    # Get flash and EEPROM size in bytes and make sure the numbers are in dec form
    FLASH_SIZE=$(${avrdude[@]} -cdryrun -T 'part -m' 2>/dev/null | grep flash | awk '{print $2}')
    EE_SIZE=$(${avrdude[@]} -cdryrun -T 'part -m' 2>/dev/null | grep eeprom | awk '{print $2}')

    if [[ -z "$FLASH_SIZE" ]]; then
      echo "Cannot detect flash; check that \"${pgm_and_target[$p]}\" are valid avrdude options; skipping this test"
      continue
    fi

    # Memories that may or may not be present
    USERSIG_SIZE=$(${avrdude[@]} -cdryrun -T 'part -m' 2>/dev/null | grep usersig | awk '{print $2}') # R/W

    # Is the to be tested programmer for a bootloader?
    is_bootloader=0
    if [ -n "$programmer" ]; then
      ($avrdude_bin $avrdude_conf "$programmer"/At 2>/dev/null | grep -q prog_modes.PM_SPM) && is_bootloader=1
    fi

    # Should we test fuses?
    fusetest=1
    [[ -n "$part" && "${nofusetest[@]}" =~ "$part/" ]] && fusetest=0

    # Should EEPROM test be carried out?
    check_eeprom=1
    [[ -z "$EE_SIZE" ]] && check_eeprom=0
    [[ $is_bootloader -eq 1 && $skip_eeprom -eq 1 ]] && check_eeprom=0

    #####
    # Dryrun tests for high-level progrmmer-independent tests (only for -m2560 or similar)
    #
    if [[ "$programmer" == -cdryrun && $FLASH_SIZE -eq 262144 ]]; then
      # Raw test
      specify="flash raw format -T/-U write/verify cola-vending-machine.raw"
      command=(${avrdude[@]}
        -T '"erase flash; write flash -512 0xc0cac01a 0xcafe \"secret Coca .bin recipe\""'
        -U flash:w:$tfiles/cola-vending-machine.raw
        -T '"write flash -1024 \"Hello World\""')
      execute "${command[@]}" > $outfile
      result [[ ! -s $outfile '&&' ! -s $logfile ]]

      specify="flash extended address and hole test"
      command=(${avrdude[@]} -U flash:w:$tfiles/blink-mega2560+lext-test.hex)
      execute "${command[@]}"
      result [ $? == 0 ]

      # Test binary, octal, decimal, hexadecimal and R number lists for I/O
      declare -A numsys=([b]=binary [o]=octal [d]=decimal [h]=hexadecimal [R]=R)
      for fmt in b o d h R; do
        specify="flash writing ${numsys[$fmt]} numbers"
        command=(${avrdude[@]}
          -U $tfiles/urboot_m2560_1s_x16m0_115k2_uart0_rxe0_txe1_led+b7_pr_ee_ce.hex
          -T '"write flash 0x3fd00 0xc0cac01a 0xcafe \"secret Coca Cola recipe\""'
          -U flash:w:$tfiles/cola-vending-machine.raw
          -U flash:r:$tmpfile:$fmt)
        execute "${command[@]}"
        result [ $? == 0 ]

        specify="flash reading and verifying ${numsys[$fmt]} numbers"
        command=(${avrdude[@]}
          -U flash:w:$tmpfile:$fmt
          -U flash:r:$resfile:r)
        execute "${command[@]}"
        result cmp -s $resfile $tfiles/expected-flash-m2560.raw
        cp /dev/null $tmpfile; cp /dev/null $resfile
      done

      specify="flash writing srec format"
      command=(${avrdude[@]}
        -U $tfiles/urboot_m2560_1s_x16m0_115k2_uart0_rxe0_txe1_led+b7_pr_ee_ce.hex
        -T '"write flash 0x3fd00 0xc0cac01a 0xcafe \"secret Coca Cola recipe\""'
        -U flash:w:$tfiles/cola-vending-machine.raw
        -U flash:r:$tmpfile:s)
      execute "${command[@]}"
      result [ $? == 0 ]

      specify="flash reading and verifying srec format file"
      command=(${avrdude[@]}
        -U flash:w:$tmpfile:s
        -U flash:v:$tfiles/expected-flash-m2560.raw:r)
      execute "${command[@]}"
      result [ $? == 0 ]
      cp /dev/null $tmpfile
    fi

    #####
    # Fuse test (bootloaders usually cannot set fuses)
    #
    if [[ $is_bootloader -ne 1 && $fusetest -eq 1 ]]; then
      if [ -n "$EE_SIZE" ]; then
        specify="fuse access: clear, set and read eesave fuse bit"
        command=(${avrdude[@]} -T '"config eesave=0; config eesave=1; config eesave"')
      else
        specify="fuse access: clear, set and read wdton fuse bit"
        command=(${avrdude[@]} -T '"config wdton=0; config wdton=1; config wdton"')
      fi
      execute "${command[@]}" > $outfile
      fusebit=$(grep ^config $outfile | awk '{print $4}')
      sed -e/^config/d -e/"> "/d $outfile > ${outfile}-2
      mv ${outfile}-2 $outfile
      result [[ '"$fusebit"' == 1 '&&' ! -s $outfile '&&' ! -s $logfile ]]

      if [ -n "$EE_SIZE" ]; then
        specify="fuse access: set eesave fusebit to delete EEPROM on chip erase"
        command=(${avrdude[@]} -T '"config eesave=ee*erased"')
        execute "${command[@]}" > $outfile
        sed -e/^config/d -e/"> "/d $outfile > ${outfile}-2
        mv ${outfile}-2 $outfile
        result [[ ! -s $outfile  '&&' ! -s $logfile ]]
      fi
    fi

    ######
    # Chip erase for defined initial state
    #
    specify="chip erase"
    command=(${avrdude[@]} -e)
    execute "${command[@]}"
    result [ $? == 0 ]

    #####
    # Flash test: a relatively difficult file with two code blocks and one data block with holes
    #
    specify="flash -U write/verify holes_rjmp_loops_${FLASH_SIZE}B.hex"
    command=(${avrdude[@]} -Uflash:w:$tfiles/holes_rjmp_loops_${FLASH_SIZE}B.hex)
    execute "${command[@]}"
    result [ $? == 0 ]
    if [ $? != 0 ]; then        # Not working? try a file without holes
      specify="flash -U write/verify rjmp_loops_for_bootloaders_${FLASH_SIZE}B.hex"
      command=(${avrdude[@]} -Uflash:w:$tfiles/rjmp_loops_for_bootloaders_${FLASH_SIZE}B.hex)
      execute "${command[@]}"
      result [ $? == 0 ]
    fi

    specify="flash -T write/verify holes_rjmp_loops_${FLASH_SIZE}B.hex"
    command=(${avrdude[@]} -T '"write flash '$tfiles/holes_rjmp_loops_${FLASH_SIZE}B.hex:a'"')
    execute "${command[@]}" > $outfile
    result [[ ! -s $outfile '&&' ! -s $logfile ]]
    if [ $? != 0 ]; then        # Not working? try a file without holes
      specify="flash -T write/verify rjmp_loops_for_bootloaders_${FLASH_SIZE}B.hex"
      command=(${avrdude[@]} -T '"write flash '$tfiles/rjmp_loops_for_bootloaders_${FLASH_SIZE}B.hex:a'"')
      execute "${command[@]}" > $outfile
      result [[ ! -s $outfile '&&' ! -s $logfile ]]
    fi

    ######
    # EEPROM tests
    #
    if [ $check_eeprom -eq 1 ]; then
      # -U cannot cope with EEPROMs that are unable to set cleared bits but
      # the terminal can if the eesave fuse makes chip erase erase EEPROM
      specify="eeprom check whether programmer can flip 0s to 1s"
      command=(${avrdude[@]}  -Ueeprom:w:0x55:m -Ueeprom:w:0xaa:m)
      execute "${command[@]}"
      result [ $? == 0 ]
      if [ $? == 0 ]; then      # OK, try a file with holes
        specify="eeprom -U write/verify holes_pack_my_box_${EE_SIZE}B.hex"
        command=(${avrdude[@]} -Ueeprom:w:$tfiles/holes_pack_my_box_${EE_SIZE}B.hex)
        execute "${command[@]}"
        result [ $? == 0 ]
        if [ $? != 0 ]; then        # Not working? try a file without holes
          specify="eeprom -U write/verify the_quick_brown_fox_${EE_SIZE}B.hex"
          command=(${avrdude[@]} -Ueeprom:w:$tfiles/the_quick_brown_fox_${EE_SIZE}B.hex)
          execute "${command[@]}"
          result [ $? == 0 ]
        fi
      else
        echo "# ... the next test may therefore take longer"
      fi

      specify="eeprom -T write/verify holes_{the_five_boxing_wizards,pack_my_box}_${EE_SIZE}B.hex"
      command=(${avrdude[@]}
        -T '"write eeprom '$tfiles/holes_the_five_boxing_wizards_${EE_SIZE}B.hex:a'"'
        -T flush
        -T '"write eeprom '$tfiles/holes_pack_my_box_${EE_SIZE}B.hex:a'"')
      execute "${command[@]}" > $outfile
      result [[ ! -s $outfile '&&' ! -s $logfile ]]
      if [ $? != 0 ]; then        # Not working? try a file without holes
        specify="eeprom -T write/verify lorem_ipsum_${EE_SIZE}B.srec"
        command=(${avrdude[@]} -T '"write eeprom '$tfiles/lorem_ipsum_${EE_SIZE}B.srec:a'"')
        execute "${command[@]}" > $outfile
        result [[ ! -s $outfile '&&' ! -s $logfile ]]
      fi
    fi

    ######
    # Chip erase and verify
    #
    specify="chip erase and spot check flash is actually erased"
    command=(${avrdude[@]} -e -Uflash:v:$tfiles/holes_flash_0xff_${FLASH_SIZE}B.hex)
    execute "${command[@]}"
    result [ $? == 0 ]
    if [[ $? == 0 && $check_eeprom -eq 1 && $is_bootloader -eq 0 ]]; then
      specify="spot check eeprom is erased, too"
      command=(${avrdude[@]} -Ueeprom:v:$tfiles/holes_eeprom_0xff_${EE_SIZE}B.hex)
      execute "${command[@]}"
      result [ $? == 0 ]
    fi

    ######
    # Write and verify random data to usersig if present
    #
    if [[ -n "$USERSIG_SIZE" && $is_bootloader -ne 1 ]]; then
      specify="usersig -T/-U write/read random_data_${USERSIG_SIZE}B.bin"
      command=(${avrdude[@]}
        -T '"erase usersig; write usersig '$tfiles/random_data_${USERSIG_SIZE}B.bin'"'
        -T flush
        -U usersig:r:$tmpfile:r
        -U usersig:v:$tmpfile:r
        -T '"erase usersig"'
        -T flush
        -U usersig:v:$tfiles/0xff_${USERSIG_SIZE}B.hex:i)
      execute "${command[@]}" >$outfile
      result [[ ! -s $outfile '&&' ! -s $logfile ]] '&&' cmp -s "$tfiles/random_data_${USERSIG_SIZE}B.bin" "$tmpfile"
      cp /dev/null $tmpfile
    fi

    if [ $FAIL == true ]; then
      echo ''
      read -rep "One or more AVRDUDE \"${pgm_and_target[$p]}\" tests failed. Do you want to retry this particular test? (y/n): " choice
      case "$choice" in
        [yY])
          p=$p-1; # Re-run the same for-loop iterator
          ;;
        *)
          # Continue with the next hardware setup in the list
          ;;
      esac
    fi

  fi #key
done #for

exit $exitstate
