#!/data/data/com.termux/files/usr/bin/bash
# shellcheck shell=bash

if [ -z "${BASH_VERSION:-}" ]; then
    echo "The 'termux-exec-tests' script must be run from a 'bash' shell."; return 64 2>/dev/null|| exit 64 # EX__USAGE
fi



termux_exec__tests__init() {

TERMUX_EXEC__TESTS__LOG_LEVEL___N="TERMUX_EXEC__TESTS__LOG_LEVEL"
termux_exec__tests__copy_variable TERMUX_EXEC__TESTS__LOG_LEVEL "$TERMUX_EXEC__TESTS__LOG_LEVEL___N" || return $?

TERMUX_EXEC__TESTS__MAX_LOG_LEVEL=5 # Default: `5` (VVVERBOSE=5)
{ [[ ! "${TERMUX_EXEC__TESTS__LOG_LEVEL:-}" =~ ^[0-9]+$ ]] || [[ "$TERMUX_EXEC__TESTS__LOG_LEVEL" -gt "$TERMUX_EXEC__TESTS__MAX_LOG_LEVEL" ]]; } && \
TERMUX_EXEC__TESTS__LOG_LEVEL=1 # Default: `1` (OFF=0, NORMAL=1, DEBUG=2, VERBOSE=3, VVERBOSE=4 and VVVERBOSE=5)
TERMUX_EXEC__TESTS__LOG_TAG="" # Default: ``

TERMUX_EXEC__TESTS__COMMAND_TYPE_ID="" # Default: ``
TERMUX_EXEC__TESTS__COMMAND_TYPE_NOOP="false" # Default: `false`

NL=$'\n'

TERMUX_EXEC__TESTS__DETECT_LEAKS=0 # Default: `0`
TERMUX_EXEC__TESTS__USE_FSANITIZE_BUILDS="false" # Default: `false`
TERMUX_EXEC__TESTS__NO_CLEAN="false" # Default: `false`
TERMUX_EXEC__TESTS__TEST_NAMES_FILTER=""

TERMUX_EXEC__TESTS__TESTS_COUNT=""
TERMUX_EXEC__TESTS__LOG_EMPTY_LINE_AFTER_SCRIPT_TEST="false"

TERMUX_EXEC__TESTS__REGEX__ABSOLUTE_PATH='^(/[^/]+)+$'
TERMUX_EXEC__TESTS__REGEX__ROOTFS_OR_ABSOLUTE_PATH='^((/)|((/[^/]+)+))$'
TERMUX_EXEC__TESTS__REGEX__UNSIGNED_INT='^[0-9]+$'



# Set `TERMUX_*` variables to environment variables exported by
# Termux app, otherwise default to build time placeholders.
# This is done to support scoped and dynamic variables design.
# The `TERMUX_ENV__*` variables still use build time placeholders.

TERMUX_APP__NAME___N="TERMUX_APP__NAME"
termux_exec__tests__copy_variable TERMUX_APP__NAME "$TERMUX_APP__NAME___N" || return $?
[[ -z "$TERMUX_APP__NAME" ]] && \
TERMUX_APP__NAME="Termux"


TERMUX__ROOTFS___N="TERMUX__ROOTFS"
termux_exec__tests__copy_variable TERMUX__ROOTFS "$TERMUX__ROOTFS___N" || return $?
[[ ! "$TERMUX__ROOTFS" =~ $TERMUX_EXEC__TESTS__REGEX__ROOTFS_OR_ABSOLUTE_PATH ]] && \
TERMUX__ROOTFS="/data/data/com.termux/files"

TERMUX__PREFIX___N="TERMUX__PREFIX"
termux_exec__tests__copy_variable TERMUX__PREFIX "$TERMUX__PREFIX___N" || return $?
[[ ! "$TERMUX__PREFIX" =~ $TERMUX_EXEC__TESTS__REGEX__ABSOLUTE_PATH ]] && \
TERMUX__PREFIX="/data/data/com.termux/files/usr"


TERMUX_EXEC__TESTS__TESTS_PATH___N="TERMUX_EXEC__TESTS__TESTS_PATH"
TERMUX_EXEC__TESTS__TESTS_PATH="$TERMUX__PREFIX/libexec/installed-tests/termux-exec"
printf -v "$TERMUX_EXEC__TESTS__TESTS_PATH___N" "%s" "$TERMUX_EXEC__TESTS__TESTS_PATH" || return $?
export "${TERMUX_EXEC__TESTS__TESTS_PATH___N?}" || return $?


TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH___N="TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH"

TERMUX_EXEC__TESTS__LD_PRELOAD_DIR="$TERMUX__PREFIX/lib"
TERMUX_EXEC__PRIMARY_LD_PRELOAD_FILE_PATH="$TERMUX__PREFIX/lib/libtermux-exec-ld-preload.so"
TERMUX_EXEC__DIRECT_LD_PRELOAD_FILE_PATH="$TERMUX__PREFIX/lib/libtermux-exec-direct-ld-preload.so"
TERMUX_EXEC__LINKER_LD_PRELOAD_FILE_PATH="$TERMUX__PREFIX/lib/libtermux-exec-linker-ld-preload.so"


# Set exit traps.
termux_exec__tests__set_traps || return $?

}



function termux_exec__tests__log() { local log_level="${1}"; shift; if [[ $TERMUX_EXEC__TESTS__LOG_LEVEL -ge $log_level ]]; then echo "${TERMUX_EXEC__TESTS__LOG_TAG:-"termux-exec.tests"}:" "$@"; fi }
function termux_exec__tests__log_literal() { local log_level="${1}"; shift; if [[ $TERMUX_EXEC__TESTS__LOG_LEVEL -ge $log_level ]]; then echo -e "${TERMUX_EXEC__TESTS__LOG_TAG:-"termux-exec.tests"}:" "$@"; fi }
function termux_exec__tests__log_error() { echo "${TERMUX_EXEC__TESTS__LOG_TAG:-"termux-exec.tests"}:" "$@" 1>&2; }



##
# `termux_exec__tests__main` [`<argument...>`]
##
termux_exec__tests__main() {

    local return_value

    termux_exec__tests__init || return $?

    TERMUX_EXEC__TESTS__RUN_UNIT_TESTS="false"
    TERMUX_EXEC__TESTS__RUN_RUNTIME_TESTS="false"

    # Process the command arguments passed to the script.
    termux_exec__tests__process_script_arguments "$@" || return $?
    if [ "$TERMUX_EXEC__TESTS__COMMAND_TYPE_NOOP" = "true" ]; then return 0; fi


    termux_exec__tests__log 4 "Running 'termux_exec__tests__main'"

    if [[ "$TERMUX_EXEC__TESTS__COMMAND_TYPE_ID" == *,* ]] || \
            [[ ",unit,runtime,all," != *",$TERMUX_EXEC__TESTS__COMMAND_TYPE_ID,"* ]]; then
        termux_exec__tests__log_error "Invalid command type id '$TERMUX_EXEC__TESTS__COMMAND_TYPE_ID' passed. Must equal 'unit', 'runtime' or 'all'."
        return 1
    fi


    termux_exec__tests__log 1 "Running 'termux-exec' tests"

    local tests_start_time; tests_start_time="$(date "+%s")" || return $?


    [[ ",unit,all," == *",$TERMUX_EXEC__TESTS__COMMAND_TYPE_ID,"* ]] && TERMUX_EXEC__TESTS__RUN_UNIT_TESTS="true"
    [[ ",runtime,all," == *",$TERMUX_EXEC__TESTS__COMMAND_TYPE_ID,"* ]] && TERMUX_EXEC__TESTS__RUN_RUNTIME_TESTS="true"


    termux_exec__tests__log 5 "$TERMUX_EXEC__TESTS__LOG_LEVEL___N='$TERMUX_EXEC__TESTS__LOG_LEVEL'"
    termux_exec__tests__log 5 "$TERMUX_EXEC__TESTS__TESTS_PATH___N='$TERMUX_EXEC__TESTS__TESTS_PATH'"
    [[ -n "$TERMUX_EXEC__TESTS__TEST_NAMES_FILTER" ]] && termux_exec__tests__log 5 "$TERMUX_EXEC__TESTS__TEST_NAMES_FILTER='$TERMUX_EXEC__TESTS__TEST_NAMES_FILTER'"


    # Set `TERMUX_EXEC__TESTS__TESTS_PATH` used by compiled c tests.
    if [[ ! "$TERMUX_EXEC__TESTS__TESTS_PATH" =~ $TERMUX_EXEC__TESTS__REGEX__ROOTFS_OR_ABSOLUTE_PATH ]]; then
        termux_exec__tests__log_error "The TERMUX_EXEC__TESTS__TESTS_PATH '$TERMUX_EXEC__TESTS__TESTS_PATH' is either not set or is not an absolute path"
        return 1
    fi


    TERMUX_EXEC__TESTS__IS_RUNNING_ON_ANDROID="false"
    TERMUX_EXEC__TESTS__IS_RUNNING_IN_TERMUX="false"
    if [ -f "/system/bin/app_process" ]; then
        TERMUX_EXEC__TESTS__IS_RUNNING_ON_ANDROID="true"
        [ -x "$TERMUX__ROOTFS" ] && TERMUX_EXEC__TESTS__IS_RUNNING_IN_TERMUX="true"
    fi


    # Setup variables for runtime tests.
    if [[ "$TERMUX_EXEC__TESTS__RUN_RUNTIME_TESTS" == "true" ]]; then
        if [[ "$TERMUX_EXEC__TESTS__IS_RUNNING_IN_TERMUX" != "true" ]]; then
            termux_exec__tests__log_error "The TERMUX__ROOTFS '$TERMUX__ROOTFS' path not found or is not executable. "
            termux_exec__tests__log_error "Runtime tests must be run from $TERMUX_APP__NAME app in Android."
            return 1
        fi


        ANDROID__BUILD_VERSION_SDK="$(getprop "ro.build.version.sdk")"
        if [[ ! "$ANDROID__BUILD_VERSION_SDK" =~ $TERMUX_EXEC__TESTS__REGEX__UNSIGNED_INT ]]; then
            termux_exec__tests__log_error "Failed to get android build version sdk with getprop"
            return 1
        fi


        # Find the `libtermux-exec-*-ld-preload.so` variant currently
        # installed at `$TERMUX__PREFIX/lib/libtermux-exec-ld-preload.so` and use
        # the same variant under `$TERMUX_EXEC__TESTS__LD_PRELOAD_DIR`.
        local primary_ld_preload_file_checksum direct_ld_preload_file_checksum linker_ld_preload_file_checksum

        primary_ld_preload_file_checksum="$(sha256sum "$TERMUX_EXEC__PRIMARY_LD_PRELOAD_FILE_PATH")" || return $?
        primary_ld_preload_file_checksum="${primary_ld_preload_file_checksum%% *}"
        direct_ld_preload_file_checksum="$(sha256sum "$TERMUX_EXEC__DIRECT_LD_PRELOAD_FILE_PATH")" || return $?
        direct_ld_preload_file_checksum="${direct_ld_preload_file_checksum%% *}"
        linker_ld_preload_file_checksum="$(sha256sum "$TERMUX_EXEC__LINKER_LD_PRELOAD_FILE_PATH")" || return $?
        linker_ld_preload_file_checksum="${linker_ld_preload_file_checksum%% *}"

        if [[ "$primary_ld_preload_file_checksum" == "$direct_ld_preload_file_checksum" ]]; then
            TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH="$TERMUX_EXEC__TESTS__LD_PRELOAD_DIR/libtermux-exec-direct-ld-preload.so"
        elif [[ "$primary_ld_preload_file_checksum" == "$linker_ld_preload_file_checksum" ]]; then
            TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH="$TERMUX_EXEC__TESTS__LD_PRELOAD_DIR/libtermux-exec-linker-ld-preload.so"
        else
            termux_exec__tests__log_error "Failed to find $TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH___N \
to use for tests as checksum of primary ld preload file did not match a variant."
    termux_exec__tests__log_error "TERMUX_EXEC__PRIMARY_LD_PRELOAD_FILE_PATH='$TERMUX_EXEC__PRIMARY_LD_PRELOAD_FILE_PATH' ($primary_ld_preload_file_checksum)"
    termux_exec__tests__log_error "TERMUX_EXEC__DIRECT_LD_PRELOAD_FILE_PATH='$TERMUX_EXEC__DIRECT_LD_PRELOAD_FILE_PATH' ($direct_ld_preload_file_checksum)"
    termux_exec__tests__log_error "TERMUX_EXEC__LINKER_LD_PRELOAD_FILE_PATH='$TERMUX_EXEC__LINKER_LD_PRELOAD_FILE_PATH' ($linker_ld_preload_file_checksum)"
            return 1
        fi

        termux_exec__tests__log 5 "$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH___N='$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH'"

        if [[ ! -f "$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH" ]]; then
            termux_exec__tests__log_error "The $TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH___N '$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH' not found."
            return 1
        fi

        printf -v "$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH___N" "%s" "$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH" || return $?
        export "${TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH___N?}" || return $?


        if [[ ! -d "$TMPDIR" ]]; then
            termux_exec__tests__log_error "The TMPDIR '$TMPDIR' is either not set or not a directory"
            return 1
        fi


        TERMUX_EXEC__TESTS__TMPDIR_PATH="$TMPDIR/termux-exec-tests"

        # Ensure test directory is clean and does not contain files from previous run.
        rm -rf "$TERMUX_EXEC__TESTS__TMPDIR_PATH" || return $?
        mkdir -p "$TERMUX_EXEC__TESTS__TMPDIR_PATH" || return $?


        # Setup temp directory for exec tests.
        # DO NOT modify as its used by `testExecIntercept__SingleAndDoubleDotExecutablePaths()`.
        TERMUX_EXEC__TESTS__EXEC_TMPDIR_PATH="$TERMUX_EXEC__TESTS__TMPDIR_PATH/exec"
        mkdir -p "$TERMUX_EXEC__TESTS__EXEC_TMPDIR_PATH" || return $?

        TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME="test-script"
        TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH="$TERMUX_EXEC__TESTS__EXEC_TMPDIR_PATH/$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME"
    fi



    # Run tests.
    termux_exec__libtermux_exec__nos__c__tests__run_command || return $?



    termux_exec__tests__log 1 "All 'termux-exec' tests successful in \
$(termux_exec__tests__print_elapsed_time "$tests_start_time")"

    return 0

}


##
# `termux_exec__libtermux_exec__nos__c__tests__run_command`
##
termux_exec__libtermux_exec__nos__c__tests__run_command() {

    local return_value

    local tests_start_time; tests_start_time="$(date "+%s")" || return $?

    termux_exec__tests__log 1 "Running 'libtermux-exec_nos_c_tre_tests'"

    (
        # shellcheck source=lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in
        termux_exec__tests__source_file_from_path \
            "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/libtermux-exec_nos_c_tre_tests" || exit $?

        libtermux_exec__nos__c__tests__main
    )
    return_value=$?
    if [ $return_value -ne 0 ]; then
        termux_exec__tests__log_error "'libtermux-exec_nos_c_tre_tests' failed"
        return $return_value
    fi

    termux_exec__tests__log 2 "'libtermux-exec_nos_c_tre_tests' completed in \
$(termux_exec__tests__print_elapsed_time "$tests_start_time")"

    return 0

}





##
# `termux_exec__tests__run_script_test` `<test_name>` [`<command_options...>`] \
#   `<test_file_content>`
#   `<expected_exit_code>` `<expected_output_regex>` \
#   [`<script_args...>`]
##
termux_exec__tests__run_script_test() {

    local return_value

    local opt; local opt_arg; local OPTARG; local OPTIND;

    local test_file_set_executable="true"
    local working_directory="."
    local execution_path=""

    if [[ $# -lt 1 ]]; then
        termux_exec__tests__log_error "Invalid argument count $#. The 'termux_exec__tests__run_script_test' command expects at least 1 argument: \
 test_name"
        return 1
    fi

    local test_name="$1"
    shift 1

    termux_exec__tests__log 5 "$test_name()"

    # Parse options to main command.
    while getopts ":-:" opt; do
        opt_arg="${OPTARG:-}"
        case "${opt}" in
            -)
                case "${OPTARG}" in *?=*) opt_arg="${OPTARG#*=}";; *) opt_arg="";; esac
                case "${OPTARG}" in
                    no-test-file-set-executable)
                        test_file_set_executable="false"
                        ;;
                    working-dir=?*)
                        working_directory="$opt_arg"
                        ;;
                    working-dir | working-dir=)
                        termux_exec__tests__log_error "No argument set for arg option '--${OPTARG%=*}'."
                        return 64 # EX__USAGE
                        ;;
                    executable-path=?*)
                        execution_path="$opt_arg"
                        ;;
                    executable-path | executable-path=)
                        termux_exec__tests__log_error "No argument set for arg option '--${OPTARG%=*}'."
                        return 64 # EX__USAGE
                        ;;
                    '')
                        # End of options `--`.
                        break
                        ;;
                    *)
                        termux_exec__tests__log_error "Unknown option: '--${OPTARG:-}'."
                        return 64 # EX__USAGE
                        ;;
                esac
                ;;
            \?)
                :;;
        esac
    done
    shift $((OPTIND - 1)) # Remove already processed arguments from argument list



    if [[ $# -lt 3 ]]; then
        termux_exec__tests__log_error "Invalid argument count $#. The 'termux_exec__tests__run_script_test' command expects at least 4 arguments: \
 test_name test_file_content expected_exit_code expected_output_regex [script_args]"
        return 1
    fi

    local test_file_content="$1"
    local expected_exit_code="$2"
    local expected_output_regex="$3"
    shift 3 # Remove args before `script_args`

    local output
    local actual_output
    local test_failed="false"

    if [[ -n "${TERMUX_EXEC__TESTS__TEST_NAMES_FILTER:-}" ]] && [[ ! "$test_name" =~ $TERMUX_EXEC__TESTS__TEST_NAMES_FILTER ]]; then
        return 0
    fi

    if [[ "${TERMUX_EXEC__TESTS__TESTS_COUNT:-}" =~ $TERMUX_EXEC__TESTS__REGEX__UNSIGNED_INT ]]; then
        TERMUX_EXEC__TESTS__TESTS_COUNT=$((TERMUX_EXEC__TESTS__TESTS_COUNT + 1))
    fi

    termux_exec__tests__log 5 "test_file_path='$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH'"
    if [[ "$test_file_content" == *"${NL}"* ]]; then
        termux_exec__tests__log 5 "test_file_content=${NL}"'```'"${NL}$test_file_content${NL}"'```'
    else
        termux_exec__tests__log 5 "test_file_content='$test_file_content'"
    fi

    if [[ "$test_file_set_executable" != "true" ]]; then
        termux_exec__tests__log 5 "test_file_set_executable='$test_file_set_executable'"
    fi
    if [[ "$working_directory" != "." ]]; then
        termux_exec__tests__log 5 "working_directory='$working_directory'"
    fi
    if [[ -n "$execution_path" ]]; then
        termux_exec__tests__log 5 "execution_path='$execution_path'"
    fi

    termux_exec__tests__log 5 "expected_exit_code='$expected_exit_code'"
    termux_exec__tests__log 5 "expected_output_regex='$expected_output_regex'"

    # If TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH is not a valid absolute path.
    if [[ ! "$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH" =~ $TERMUX_EXEC__TESTS__REGEX__ABSOLUTE_PATH ]]; then
        termux_exec__tests__log_error "The TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH '$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH' is not a valid absolute path"
        return 1
    fi

    rm -f "$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH" || return $?

    output="$(printf "%s" "$test_file_content" > "$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH" 2>&1)"
    return_value=$?
    if [ $return_value -ne 0 ]; then
        printf "%s\n" "$output" 1>&2
        termux_exec__tests__log_error "Failed to create the '$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH' file for the '$test_name' test"
        return $return_value
    fi

    if [[ "$test_file_set_executable" == "true" ]]; then
        output="$(chmod +x "$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH" 2>&1)"
        return_value=$?
        if [ $return_value -ne 0 ]; then
            printf "%s\n" "$output" 1>&2
            termux_exec__tests__log_error "Failed to set the executable bit for the '$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH' file for the '$test_name' test"
            return $return_value
        fi
    fi

    actual_output="$(cd "$working_directory" && "${execution_path:-$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH}" "$@" 2>&1)"
    actual_exit_code=$?
    if [[ -n "$expected_output_regex" ]] && [[ ! "$actual_output" =~ $expected_output_regex ]]; then
        termux_exec__tests__log_error "FAILED: '$test_name' test"
        termux_exec__tests__log_error "Expected output_regex does not equal match actual output"
        test_failed="true"
    elif [ $actual_exit_code != "$expected_exit_code" ]; then
        termux_exec__tests__log_error "$actual_output"
        termux_exec__tests__log_error "FAILED: '$test_name' test"
        termux_exec__tests__log_error "Expected result_code does not equal actual result_code"
        test_failed="true"
    fi

    if [[ "$test_failed" == "true" ]]; then
        if [[ "$test_file_content" == *"${NL}"* ]]; then
            termux_exec__tests__log_error "test_file_content=${NL}"'```'"${NL}$test_file_content${NL}"'```'
        else
            termux_exec__tests__log_error "test_file_content='$test_file_content'"
        fi
        termux_exec__tests__log_error "actual_exit_code: '$actual_exit_code'"
        termux_exec__tests__log_error "expected_exit_code: '$expected_exit_code'"
        termux_exec__tests__log_error "actual_output: '$actual_output'"
        termux_exec__tests__log_error "expected_output_regex: '$expected_output_regex'"
        return 100
    else
        #termux_exec__tests__log 2 "PASSED"

        # Remove test file so that later tests do not accidentally use it.
        rm -f "$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH" || return $?

        if [[ "$TERMUX_EXEC__TESTS__LOG_EMPTY_LINE_AFTER_SCRIPT_TEST" == "true" ]]; then
            termux_exec__tests__log 5 ""
        fi

        return 0
    fi

}





##
# Source a file under `$PATH`, like under `TERMUX__PREFIX/bin`.
#
# A separate function is used to source so that arguments passed to
# calling script/function are not passed to the sourced script.
#
#
# termux_exec__tests__source_file_from_path <file_name>
##
termux_exec__tests__source_file_from_path() {

    local source_file="${1:-}"; [ $# -gt 0 ] && shift 1;

    local source_path

    if source_path="$(command -v "$source_file")" && [ -n "$source_path" ]; then
        # shellcheck disable=SC1090
        source "$source_path" || return $?
    else
        echo "Failed to find the '$source_file' file to source." 1>&2
        return 1
    fi

}



##
# Copy the value of a variable to another variable.
#
#
# **Parameters:**
# `output_variable_name` - The name of the output variable to set.
# `input_variable_name` - The name of the input variable to read.
#
# **Returns:**
# Returns `0` if successful, otherwise returns with a non-zero exit code.
#
#
# `termux_exec__tests__copy_variable` `<output_variable_name>` `<input_variable_name>`
##
termux_exec__tests__copy_variable() {

    local output_variable_name="${1:-}"
    local input_variable_name="${2:-}"

    if [[ ! "$output_variable_name" =~ ^[a-zA-Z][a-zA-Z0-9_]*$ ]]; then
        echo "The output_variable_name '$output_variable_name' is not a valid shell variable name while running 'termux_exec__tests__copy_variable'." 1>&2
        return 1
    fi

    if [[ ! "$input_variable_name" =~ ^[a-zA-Z][a-zA-Z0-9_]*$ ]]; then
        echo "The input_variable_name '$input_variable_name' is not a valid shell variable name while running 'termux_exec__tests__copy_variable'." 1>&2
        return 1
    fi

    eval "$output_variable_name"=\"\$\{"$input_variable_name":-\}\"

}



##
# Escape '\$[](){}|^.?+*' in a string with backslashes so that it can
# be used as a literal string in regex.
#
#
# `termux_exec__tests__escape_string_for_regex` `<string>`
##
termux_exec__tests__escape_string_for_regex() {

    printf "%s" "$1" | sed -zE -e 's/[][\.|$(){}?+*^]/\\&/g'

}



##
# `termux_exec__tests__print_elapsed_time` `<start_time>`
##
termux_exec__tests__print_elapsed_time() {

    local start_time="$1"

    local end_time

    end_time=$(($(date "+%s") - start_time)) || return $?

    printf "%s" "$((end_time / 3600 )) hours $(((end_time % 3600) / 60)) minutes $((end_time % 60)) seconds"

}





##
# Set exit traps to `termux_exec__tests__traps()`.
##
termux_exec__tests__set_traps() {

    # Set traps to `termux_exec__tests__traps`.
    trap 'termux_exec__tests__traps' EXIT
    trap 'termux_exec__tests__traps TERM' TERM
    trap 'termux_exec__tests__traps INT' INT
    trap 'termux_exec__tests__traps HUP' HUP
    trap 'termux_exec__tests__traps QUIT' QUIT

    return 0

}

termux_exec__tests__traps_killtree() {

    local signal="$1"; local pid="$2"; local cpid
    for cpid in $(pgrep -P "$pid"); do termux_exec__tests__traps_killtree "$signal" "$cpid"; done
    [[ "$pid" != "$$" ]] && signal="${signal:=15}"; kill "-$signal" "$pid" 2>/dev/null

}

termux_exec__tests__traps() {

    local exit_code=$?
    trap - EXIT

    if [[ "${TERMUX_EXEC__TESTS__TMPDIR_PATH:-}" =~ ^(/[^/]+)+$ ]] && [[ "$TERMUX_EXEC__TESTS__NO_CLEAN" != "true" ]]; then
        rm -rf "$TERMUX_EXEC__TESTS__TMPDIR_PATH"
    fi

    [ -n "${1:-}" ] && trap - "$1";
    termux_exec__tests__traps_killtree "${1:-}" $$;
    exit $exit_code

}





##
# `termux_exec__tests__process_script_arguments` [`<argument...>`]
##
termux_exec__tests__process_script_arguments() {

    local opt; local opt_arg; local OPTARG; local OPTIND;

    if [ $# -eq 0 ]; then
        TERMUX_EXEC__TESTS__COMMAND_TYPE_NOOP="true"
        show_help; return $?
    fi

    # Parse options to main command.
    while getopts ":hqvfl-:" opt; do
        opt_arg="${OPTARG:-}"
        case "${opt}" in
            -)
                case "${OPTARG}" in *?=*) opt_arg="${OPTARG#*=}";; *) opt_arg="";; esac
                case "${OPTARG}" in
                    help)
                        TERMUX_EXEC__TESTS__COMMAND_TYPE_NOOP="true"
                        show_help; return $?
                        ;;
                    version)
                        TERMUX_EXEC__TESTS__COMMAND_TYPE_NOOP="true"
                        echo "termux-exec-tests version=1:2.3.0 org=termux project=termux-exec-package"; return $?
                        ;;
                    quiet)
                        TERMUX_EXEC__TESTS__LOG_LEVEL=0
                        ;;
                    ld-preload-dir=?*)
                        TERMUX_EXEC__TESTS__LD_PRELOAD_DIR="$(readlink -f -- "$opt_arg")" || return $?
                        ;;
                    ld-preload-dir | ld-preload-dir=)
                        termux_exec__tests__log_error "No argument set for arg option '--${OPTARG%=*}'."
                        return 64 # EX__USAGE
                        ;;
                    no-clean)
                        TERMUX_EXEC__TESTS__NO_CLEAN="true"
                        ;;
                    test-names-filter=?*)
                        TERMUX_EXEC__TESTS__TEST_NAMES_FILTER="$opt_arg" || return $?
                        ;;
                    test-names-filter | test-names-filter=)
                        termux_exec__tests__log_error "No argument set for arg option '--${OPTARG%=*}'."
                        return 64 # EX__USAGE
                        ;;
                    tests-path=?*)
                        TERMUX_EXEC__TESTS__TESTS_PATH="$(readlink -f -- "$opt_arg")" || return $?
                        ;;
                    tests-path | tests-path=)
                        termux_exec__tests__log_error "No argument set for arg option '--${OPTARG%=*}'."
                        return 64 # EX__USAGE
                        ;;
                    '')
                        # End of options `--`.
                        break
                        ;;
                    *)
                        termux_exec__tests__log_error "Unknown option: '--${OPTARG:-}'."
                        return 64 # EX__USAGE
                        ;;
                esac
                ;;
            h)
                TERMUX_EXEC__TESTS__COMMAND_TYPE_NOOP="true"
                show_help; return $?
                ;;
            q)
                TERMUX_EXEC__TESTS__LOG_LEVEL=0
                ;;
            v)
                if [ "$TERMUX_EXEC__TESTS__LOG_LEVEL" -lt "$TERMUX_EXEC__TESTS__MAX_LOG_LEVEL" ]; then
                    TERMUX_EXEC__TESTS__LOG_LEVEL=$((TERMUX_EXEC__TESTS__LOG_LEVEL+1));
                else
                    termux_exec__tests__log_error "Invalid option, max log level is $TERMUX_EXEC__TESTS__MAX_LOG_LEVEL."
                    return 64 # EX__USAGE
                fi
                ;;
            f)
                TERMUX_EXEC__TESTS__USE_FSANITIZE_BUILDS="true"
                ;;
            l)
                TERMUX_EXEC__TESTS__DETECT_LEAKS=1
                ;;
            \?)
                :;;
        esac
    done
    shift $((OPTIND - 1)) # Remove already processed arguments from argument list

    if [ $# -eq 0 ]; then
        termux_exec__tests__log_error "The command type not passed."
        return 64 # EX__USAGE
    elif [ $# -ne 1 ]; then
        termux_exec__tests__log_error "Expected 1 argument for command type but passed: $*"
        return 64 # EX__USAGE
    fi

    TERMUX_EXEC__TESTS__COMMAND_TYPE_ID="$1"

    return 0;

}

##
# `show_help`
##
show_help() {

    cat <<'HELP_EOF'
termux-exec-tests can be used to run tests for 'termux-exec'.


Usage:
    termux-exec-tests [command_options] <command>

Available commands:
    unit                      Run unit tests.
    runtime                   Run runtime on-device tests.
    all                       Run all tests.

Available command_options:
    [ -h | --help ]           Display this help screen.
    [ --version ]             Display version.
    [ -q | --quiet ]          Set log level to 'OFF'.
    [ -v | -vv | -vvv | -vvvvv ]
                              Set log level to 'DEBUG', 'VERBOSE',
                              'VVERBOSE' and 'VVVERBOSE'.
    [ -f ]                    Use fsanitize binaries for AddressSanitizer.
    [ -l ]                    Detect memory leaks with LeakSanitizer.
                              Requires '-f' to be passed.
    [ --ld-preload-dir=<dir> ]
                              The directory containing `$LD_PRELOAD'
                              libraries: 'libtermux-exec*.so'.
    [ --no-clean ]            Do not clean test files on failure.
    [ --test-names-filter=<filter> ]
                              Regex to filter which tests to run by
                              test name.
    [ --tests-path=<path> ]   The path to installed-tests directory.
HELP_EOF

}

# If script is sourced, return with success, otherwise call main function.
# - https://stackoverflow.com/a/28776166/14686958
# - https://stackoverflow.com/a/29835459/14686958
if (return 0 2>/dev/null); then
    return 0 # EX__SUCCESS
else
    termux_exec__tests__main "$@"
    exit $?
fi
