#!/usr/bin/env bash

#
# Use (to override results)
#
# NDPI_FORCE_UPDATING_UTESTS_RESULTS=1 ./do.sh 
#
# To run tests in parallel: (you need **GNU** `parallel` program)
#
# NDPI_FORCE_PARALLEL_UTESTS=1 ./do.sh

cd "$(dirname "${0}")" || exit 1

FUZZY_TESTING_ENABLED=@BUILD_FUZZTARGETS@
if [ "${NDPI_DISABLE_FUZZY}" = "1" ]; then
   FUZZY_TESTING_ENABLED=0
fi

FORCE_UPDATING_UTESTS_RESULTS=0
if [ "${NDPI_FORCE_UPDATING_UTESTS_RESULTS}" = "1" ]; then
   FORCE_UPDATING_UTESTS_RESULTS=1
fi

FORCE_PARALLEL_UTESTS=0
if [ "${NDPI_FORCE_PARALLEL_UTESTS}" = "1" ]; then
   FORCE_PARALLEL_UTESTS=1
fi

SKIP_PARALLEL_BAR=0
if [ "${NDPI_SKIP_PARALLEL_BAR}" = "1" ]; then
   SKIP_PARALLEL_BAR=1
fi

TEST_ONLY_RECENTLY_UPDATED_PCAPS=0
if [ "${NDPI_TEST_ONLY_RECENTLY_UPDATED_PCAPS}" = "1" ]; then
   TEST_ONLY_RECENTLY_UPDATED_PCAPS=1
fi

#Remember: valgrind and *SAN are incompatible!
CMD_PREFIX="${CMD_PREFIX}"
if [ "${NDPI_TESTS_WINE}" = "1" ]; then
   CMD_PREFIX="wine"
elif [ "${NDPI_TESTS_VALGRIND}" = "1" ]; then
   CMD_PREFIX="valgrind -q --leak-check=full"
fi

CMD_DIFF="$(which diff)"
CMD_WDIFF="$(which wdiff)"
CMD_COLORDIFF="$(which colordiff)"

EXE_SUFFIX=@EXE_SUFFIX@
GPROF_ENABLED=@GPROF_ENABLED@
PCRE2_ENABLED=@PCRE2_ENABLED@
PCRE_PCAPS="WebattackRCE.pcap"
NBPF_ENABLED=@NBPF_ENABLED@
NBPF_PCAPS="h323-overflow.pcap"
GLOBAL_CONTEXT_ENABLED=@GLOBAL_CONTEXT_ENABLED@
GLOBAL_CONTEXT_CFGS="caches_global"
READER="${CMD_PREFIX} ../../../example/ndpiReader${EXE_SUFFIX} --cfg=filename.config,../../../example/config.txt -A -p ../../../example/protos.txt -c ../../../example/categories.txt -r ../../../example/risky_domains.txt -j ../../../example/ja4_fingerprints.csv -S ../../../example/sha1_fingerprints.csv -G ../../../lists -q -K JSON -k /dev/null -t -v 2"


#These exports are used in parallel mode
export CMD_DIFF
export CMD_WDIFF
export CMD_COLORDIFF
export PCRE2_ENABLED
export PCRE_PCAPS
export NBPF_ENABLED
export NBPF_PCAPS
export READER
export FORCE_UPDATING_UTESTS_RESULTS
export FORCE_PARALLEL_UTESTS


RC=0

if [ ! -x "../example/ndpiReader${EXE_SUFFIX}" ]; then
  echo "$0: Missing ../example/ndpiReader${EXE_SUFFIX}"
  echo "$0: Run ./configure and make first"
  exit 1
fi

#For parallel tests you need `parallel` from GNU, not from `moreutils` package!
#On Ubuntu, for example, you might need to explicitly run `apt install parallel`
if [ $FORCE_PARALLEL_UTESTS -eq 1 ]; then
  if ! parallel -V | grep -qoE 'GNU parallel'; then
    echo "$0: To run the test in parallel mode you need **GNU** 'parallel'"
    echo "$0: Try something like 'apt install parallel'"
    exit 1
  fi
fi

if [ ${GPROF_ENABLED} -eq 1 ]; then
  GPROF="${GPROF:-$(which pprof)}"
  if [ ! -x "${GPROF}" ]; then
    echo "$0: ${GPROF} not found or not executable"
    exit 1
  fi
  echo "$0: Using pprof executable ${GPROF}"
  echo "$0: Please make sure that you use google-pprof and not gperftools"
  echo "$0: See https://github.com/google/pprof"
else
  GPROF=false
fi

fuzzy_testing() {
    if [ -f ../fuzz/fuzz_ndpi_reader ]; then
	cp ../example/protos.txt .
	cp ../example/categories.txt .
	cp ../example/risky_domains.txt .
	cp ../example/ja4_fingerprints.csv .
	cp ../example/sha1_fingerprints.csv .
	../fuzz/fuzz_ndpi_reader -dict=../fuzz/dictionary.dict -max_total_time="${MAX_TOTAL_TIME:-592}" -print_pcs=1 -workers="${FUZZY_WORKERS:-0}" -jobs="${FUZZY_JOBS:-0}" cfgs/default/pcap/
	rm -f protos.txt categories.txt risky_domains.txt ja4_fingerprints.csv sha1_fingerprints.csv
    fi
}

run_single_pcap()
{
    f=$1

    if [ ! -f "./pcap/$f" ]; then
        return 0
    fi

    SKIP_PCAP=0;
    if [ $PCRE2_ENABLED -eq 0 ]; then
        for p in $PCRE_PCAPS; do
            if [ "$f" = "$p" ]; then
                SKIP_PCAP=1
                break
            fi
        done
    fi
    if [ $NBPF_ENABLED -eq 0 ]; then
        for p in $NBPF_PCAPS; do
            if [ "$f" = "$p" ]; then
                SKIP_PCAP=1
                break
            fi
        done
    fi
    if [ $SKIP_PCAP -eq 1 ]; then
        if [ $FORCE_PARALLEL_UTESTS -eq 1 ]; then
            printf "SKIPPED\n"
        else
            printf "%-48s\tSKIPPED\n" "$f"
        fi
        return 0
    fi

    CMD="$READER -i pcap/$f -w /tmp/reader.$$.out $READER_EXTRA_PARAM"
    CPUPROFILE=./result/$f.cprof HEAPPROFILE=./result/$f $CMD
    CMD_RET=$?
    if [ $CMD_RET -eq 0 ] && [ -f /tmp/reader.$$.out ]; then
	# create result files if not present
        if [ ! -f "result/$f.out" ]; then
            cp /tmp/reader.$$.out "result/$f.out"
	fi
        NUM_DIFF=$(${CMD_DIFF} "result/$f.out" /tmp/reader.$$.out | wc -l)
    else
        if [ $FORCE_PARALLEL_UTESTS -eq 1 ]; then
            printf "ERROR (ndpiReader${EXE_SUFFIX} exit code: ${CMD_RET})\n"
        else
            printf "%-48s\tERROR (ndpiReader${EXE_SUFFIX} exit code: ${CMD_RET})\n" "$f"
	    FAILURES+=("$f") #TODO: find a way to update this variable also in parallel mode
        fi
	return 1
    fi

    if [ "$NUM_DIFF" -eq 0 ]; then
        if [ $FORCE_PARALLEL_UTESTS -eq 0 ]; then
            printf "%-48s\tOK\n" "$f"
        fi
        return 0
    else
        if [ $FORCE_PARALLEL_UTESTS -eq 1 ]; then
            printf "ERROR\n"
        else
            printf "%-48s\tERROR\n" "$f"
	    FAILURES+=("$f") #TODO: find a way to update this variable also in parallel mode
	fi
        echo "$CMD [old vs new]"
        ${CMD_DIFF} "result/$f.out" /tmp/reader.$$.out
        if [ ! -z "${CMD_COLORDIFF}" ] && [ ! -z "${CMD_WDIFF}" ]; then
            ${CMD_WDIFF} -n -3 "result/$f.out" /tmp/reader.$$.out | sort | uniq | ${CMD_COLORDIFF}
        fi
        if [ $FORCE_UPDATING_UTESTS_RESULTS -eq 1 ]; then
            cp /tmp/reader.$$.out "result/$f.out"
        fi
    fi

    /bin/rm -f /tmp/reader.$$.out

    return 1
}
export -f run_single_pcap

check_results() {
    if [ $FORCE_PARALLEL_UTESTS -eq 1 ]; then
        if [ $SKIP_PARALLEL_BAR -eq 1 ]; then
            parallel --tag "run_single_pcap" ::: $PCAPS
        else
            parallel --bar --tag "run_single_pcap" ::: $PCAPS
        fi
	RET=$? #Number of failed job up to 100
	RC=$(( RC + RET ))
    else
        for f in $PCAPS; do
	    run_single_pcap "$f"
	    RET=$?
	    RC=$(( RC + RET ))
        done
    fi

    if [ ${GPROF_ENABLED} -eq 1 ]; then
      GPROF_ARGS='-nodecount 100 -nodefraction 0 -symbolize=fastlocal'
      ${GPROF} -top ${GPROF_ARGS} ../../../example/ndpiReader${EXE_SUFFIX} ./result/*.cprof || exit 1
      ${GPROF} -png -output ./result/cpu_profile.png ${GPROF_ARGS} ../../../example/ndpiReader${EXE_SUFFIX} ./result/*.cprof || exit 1
      ${GPROF} -top ${GPROF_ARGS} -sample_index=alloc_space  ../../../example/ndpiReader${EXE_SUFFIX} ./result/*.heap || exit 1
      ${GPROF} -png -output ./result/heap_profile.png ${GPROF_ARGS} -sample_index=alloc_space ../../../example/ndpiReader${EXE_SUFFIX} ./result/*.heap || exit 1
    fi
}

if [ $FUZZY_TESTING_ENABLED -eq 1 ]; then
    fuzzy_testing
fi

for d in $(find ./cfgs/* -type d -maxdepth 0 2>/dev/null) ; do

    SKIP_CFG=0
    if [ $GLOBAL_CONTEXT_ENABLED -eq 0 ]; then
        for c in $GLOBAL_CONTEXT_CFGS; do
            if [ "$c" = "$(basename "$d")" ]; then
	        SKIP_CFG=1
	        break
	    fi
        done
    fi
    if [ $SKIP_CFG -eq 1 ]; then
        printf "Configuration \"$(basename "$d")\" %-18s\tSKIPPED\n"
        continue
    fi

    cd ./cfgs/"$(basename "$d")" || exit 1

    if [ $TEST_ONLY_RECENTLY_UPDATED_PCAPS -eq 1 ]; then
        # Latest 10 pcaps updatetd recently (to be more precise, pcap which triggered a change
        # in their results lately)
        PCAPS=$(cd result || exit 1; find * -type f -print0  | xargs -0 ls -t | head -10 | xargs basename -s .out)
    else
        if [ "$#" -ne 0 ]; then
            PCAPS=$*
        else
            PCAPS=$(cd pcap || exit 1; /bin/ls -- *.*cap*)
        fi
    fi
    FAILURES=()
    READER_EXTRA_PARAM=""
    [ -f config.txt ] && READER_EXTRA_PARAM=$(< config.txt)
    export READER_EXTRA_PARAM

    echo "Run configuration \"$(basename "$d")\" [$READER_EXTRA_PARAM]"

    check_results

    test ${#FAILURES} -ne 0 && printf '%s: %s\n' "${0}" "${RC} pcap(s) failed"
    test ${#FAILURES} -ne 0 && echo "Failed: " "${FAILURES[@]}"

    cd ../../
done

exit $RC