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

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

termux_core__tests__init() {

TERMUX_CORE__TESTS__LOG_LEVEL___N="TERMUX_CORE__TESTS__LOG_LEVEL"
termux_core__tests__copy_variable TERMUX_CORE__TESTS__LOG_LEVEL "$TERMUX_CORE__TESTS__LOG_LEVEL___N" || return $?

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

TERMUX_CORE__TESTS__COMMAND_TYPE_ID="" # Default: ``
TERMUX_CORE__TESTS__COMMAND_TYPE_NOOP="false" # Default: `false`

NL=$'\n'

TERMUX_CORE__TESTS__DETECT_LEAKS=0 # Default: `0`
TERMUX_CORE__TESTS__USE_FSANITIZE_BUILDS="false" # Default: `false`
TERMUX_CORE__TESTS__NO_CLEAN="false" # Default: `false`
[[ ! "${TERMUX_CORE__TESTS__SKIP_TERMUX_CORE_ENV_VARIABLE_TESTS:-}" =~ ^true|false$ ]] && \
TERMUX_CORE__TESTS__SKIP_TERMUX_CORE_ENV_VARIABLE_TESTS="false" # Default: `false`
TERMUX_CORE__TESTS__TEST_NAMES_FILTER=""
TERMUX_CORE__TESTS__TEST_PACKAGES_FILTER="^.*$" # Default: `^.*$`

TERMUX_CORE__TESTS__TESTS_COUNT="0"
TERMUX_CORE__TESTS__LOG_EMPTY_LINE_AFTER_SCRIPT_TEST="false"

TERMUX_CORE__TESTS__NAME_MAX=255
TERMUX_CORE__TESTS__REGEX__ABSOLUTE_PATH='^(/[^/]+)+$'
TERMUX_CORE__TESTS__REGEX__ROOTFS_OR_ABSOLUTE_PATH='^((/)|((/[^/]+)+))$'
TERMUX_CORE__TESTS__REGEX__APP_PACKAGE_NAME="^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$"
TERMUX_CORE__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_ENV__S_ROOT="TERMUX_"


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

TERMUX_APP__PACKAGE_NAME___N="TERMUX_APP__PACKAGE_NAME"
termux_core__tests__copy_variable TERMUX_APP__PACKAGE_NAME "$TERMUX_APP__PACKAGE_NAME___N" || return $?
{ [[ ! "$TERMUX_APP__PACKAGE_NAME" =~ $TERMUX_CORE__TESTS__REGEX__APP_PACKAGE_NAME ]] || \
    [ "${#TERMUX_APP__PACKAGE_NAME}" -gt $TERMUX_CORE__TESTS__NAME_MAX ]; } && \
TERMUX_APP__PACKAGE_NAME="com.termux"

TERMUX_APP__DATA_DIR___N="TERMUX_APP__DATA_DIR"
termux_core__tests__copy_variable TERMUX_APP__DATA_DIR "$TERMUX_APP__DATA_DIR___N" || return $?
[[ ! "$TERMUX_APP__DATA_DIR" =~ $TERMUX_CORE__TESTS__REGEX__ABSOLUTE_PATH ]] && \
TERMUX_APP__DATA_DIR="/data/data/com.termux"


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

TERMUX__HOME___N="TERMUX__HOME"
termux_core__tests__copy_variable TERMUX__HOME "$TERMUX__HOME___N" || return $?
[[ ! "$TERMUX__HOME" =~ $TERMUX_CORE__TESTS__REGEX__ABSOLUTE_PATH ]] && \
TERMUX__HOME="/data/data/com.termux/files/home"

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


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


TERMUX_CORE__TESTS__TESTS_PATH___N="TERMUX_CORE__TESTS__TESTS_PATH"
TERMUX_CORE__TESTS__TESTS_PATH="$TERMUX__PREFIX/libexec/installed-tests/termux-core"
printf -v "$TERMUX_CORE__TESTS__TESTS_PATH___N" "%s" "$TERMUX_CORE__TESTS__TESTS_PATH" || return $?
export "${TERMUX_CORE__TESTS__TESTS_PATH___N?}" || return $?


TERMUX__USER_ID___N="TERMUX__USER_ID"
termux_core__tests__copy_variable TERMUX__USER_ID "$TERMUX__USER_ID___N" || return $?
TERMUX__USER_ID="${TERMUX__USER_ID:-0}"


TERMUX_CORE__APPS_INFO_ENV_FILE___N="TERMUX_CORE__APPS_INFO_ENV_FILE"
termux_core__tests__copy_variable TERMUX_CORE__APPS_INFO_ENV_FILE "$TERMUX_CORE__APPS_INFO_ENV_FILE___N" || return $?



# Set exit traps.
termux_core__tests__set_traps || return $?

}



function termux_core__tests__log() { local log_level="${1}"; shift; if [[ $TERMUX_CORE__TESTS__LOG_LEVEL -ge $log_level ]]; then echo "${TERMUX_CORE__TESTS__LOG_TAG:-"termux-core.tests"}:" "$@"; fi }
function termux_core__tests__log_literal() { local log_level="${1}"; shift; if [[ $TERMUX_CORE__TESTS__LOG_LEVEL -ge $log_level ]]; then echo -e "${TERMUX_CORE__TESTS__LOG_TAG:-"termux-core.tests"}:" "$@"; fi }
function termux_core__tests__log_error() { echo "${TERMUX_CORE__TESTS__LOG_TAG:-"termux-core.tests"}:" "$@" 1>&2; }


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

    local return_value

    termux_core__tests__init || return $?

    TERMUX_CORE__TESTS__RUN_UNIT_TESTS="false"
    TERMUX_CORE__TESTS__RUN_RUNTIME_TESTS="false"

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


    termux_core__tests__log 4 "Running 'termux_core__tests__main'"

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


    termux_core__tests__log 1 "Running 'termux-core' tests"

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

    [[ ",unit,all," == *",$TERMUX_CORE__TESTS__COMMAND_TYPE_ID,"* ]] && TERMUX_CORE__TESTS__RUN_UNIT_TESTS="true"
    [[ ",runtime,all," == *",$TERMUX_CORE__TESTS__COMMAND_TYPE_ID,"* ]] && TERMUX_CORE__TESTS__RUN_RUNTIME_TESTS="true"

    termux_core__tests__log 5 "$TERMUX_CORE__TESTS__LOG_LEVEL___N='$TERMUX_CORE__TESTS__LOG_LEVEL'"
    [[ -n "$TERMUX_CORE__TESTS__TEST_NAMES_FILTER" ]] && termux_core__tests__log 5 "$TERMUX_CORE__TESTS__TEST_NAMES_FILTER='$TERMUX_CORE__TESTS__TEST_NAMES_FILTER'"
    termux_core__tests__log 5 "$TERMUX_CORE__TESTS__TESTS_PATH___N='$TERMUX_CORE__TESTS__TESTS_PATH'"
    termux_core__tests__log 5 "$TERMUX__USER_ID___N='$TERMUX__USER_ID'"


    # Set `TERMUX_CORE__TESTS__TESTS_PATH.
    if [[ ! "$TERMUX_CORE__TESTS__TESTS_PATH" =~ $TERMUX_CORE__TESTS__REGEX__ROOTFS_OR_ABSOLUTE_PATH ]]; then
        termux_core__tests__log_error "The TERMUX_CORE__TESTS__TESTS_PATH '$TERMUX_CORE__TESTS__TESTS_PATH' is either not set or is not an absolute path"
        return 1
    fi


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


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


        if [[ ! "$TERMUX__USER_ID" =~ ^(([0-9])|([1-9][0-9]{0,2}))$ ]]; then
            termux_core__tests__log_error "The TERMUX__USER_ID '$TERMUX__USER_ID' is not set to a valid user id."
            return 1
        fi


        TERMUX_CORE__TESTS__UID="$(id -u)"
        return_value=$?
        if [ $return_value -ne 0 ]; then
            termux_core__tests__log_error "Failed to get uid"
            return $return_value
        fi


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


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


        TERMUX_CORE__TESTS__TMPDIR_PATH="$TMPDIR/termux-core-tests"

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

        # Setup temp directory for exec tests.
        # DO NOT modify unless its ensured that no test requires the specified directory hierarchy.
        TERMUX_CORE__TESTS__EXEC_TMPDIR_PATH="$TERMUX_CORE__TESTS__TMPDIR_PATH/exec"
        mkdir -p "$TERMUX_CORE__TESTS__EXEC_TMPDIR_PATH" || return $?

        TERMUX_CORE__TESTS__SCRIPT_TEST_FILE_NAME="test-script"
        TERMUX_CORE__TESTS__SCRIPT_TEST_FILE_PATH="$TERMUX_CORE__TESTS__EXEC_TMPDIR_PATH/$TERMUX_CORE__TESTS__SCRIPT_TEST_FILE_NAME"
    fi



    # Run tests.
    if [[ "termux-core" =~ $TERMUX_CORE__TESTS__TEST_PACKAGES_FILTER ]]; then
        termux_core__libtermux_core__nos__c__tests__run_command || return $?
        termux_core__termux_core_main_app__tests__run_command || return $?
    fi

    termux_core__termux_external_packages__tests__run_command || return $?



    termux_core__tests__log 1 "All 'termux-core' tests successful in \
$(termux_core__tests__print_elapsed_time "$tests_start_time")"

    return 0

}



##
# `termux_core__libtermux_core__nos__c__tests__run_command`
##
termux_core__libtermux_core__nos__c__tests__run_command() {

    local return_value

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

    termux_core__tests__log 1 "Running 'libtermux-core_nos_c_tre_tests'"

    (
        # shellcheck source=lib/termux-core_nos_c_tre/tests/libtermux-core_nos_c_tre_tests.in
        termux_core__tests__source_file_from_path \
            "$TERMUX_CORE__TESTS__TESTS_PATH/lib/termux-core_nos_c_tre/libtermux-core_nos_c_tre_tests" || exit $?

        libtermux_core__nos__c__tests__main
    )
    return_value=$?
    if [ $return_value -ne 0 ]; then
        termux_core__tests__log_error "'libtermux-core_nos_c_tre_tests' failed"
        return $return_value
    fi

    termux_core__tests__log 2 "'libtermux-core_nos_c_tre_tests' completed in \
$(termux_core__tests__print_elapsed_time "$tests_start_time")"

    return 0

}



##
# `termux_core__termux_core_main_app__tests__run_command`
##
termux_core__termux_core_main_app__tests__run_command() {

    local return_value

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

    termux_core__tests__log 1 "Running 'termux-core-main-app_tests'"

    (
        # shellcheck source=app/main/tests/termux-core-main-app_tests.in
        termux_core__tests__source_file_from_path \
            "$TERMUX_CORE__TESTS__TESTS_PATH/app/main/termux-core-main-app_tests" || exit $?

        termux_core_main_app__tests__main
    )
    return_value=$?
    if [ $return_value -ne 0 ]; then
        termux_core__tests__log_error "'termux-core-main-app_tests' failed"
        return $return_value
    fi

    termux_core__tests__log 2 "'termux-core-main-app_tests' completed in \
$(termux_core__tests__print_elapsed_time "$tests_start_time")"

    return 0

}



##
# `termux_core__termux_external_packages__tests__run_command`
##
termux_core__termux_external_packages__tests__run_command() {

    local return_value

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

    termux_core__tests__log 1 "Running 'termux-external-packages_tests'"

    (
        # shellcheck source=app/main/tests/termux-external-packages_tests.in
        termux_core__tests__source_file_from_path \
            "$TERMUX_CORE__TESTS__TESTS_PATH/app/main/termux-external-packages_tests" || exit $?

        termux_external_packages__tests__main
    )
    return_value=$?
    if [ $return_value -ne 0 ]; then
        termux_core__tests__log_error "'termux-external-packages_tests' failed"
        return $return_value
    fi

    termux_core__tests__log 2 "'termux-external-packages_tests' completed in \
$(termux_core__tests__print_elapsed_time "$tests_start_time")"

    return 0

}





##
# `termux_core__tests__run_script_test` `<test_name>` [`<command_options...>`] \
#   `<test_file_content>`
#   `<expected_exit_code>` `<expected_output_regex>` \
#   [`<script_args...>`]
##
termux_core__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_core__tests__log_error "Invalid argument count $#. The 'termux_core__tests__run_script_test' command expects at least 1 argument: \
 test_name"
        return 1
    fi

    local test_name="$1"
    shift 1

    termux_core__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_core__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_core__tests__log_error "No argument set for arg option '--${OPTARG%=*}'."
                        return 64 # EX__USAGE
                        ;;
                    '')
                        # End of options `--`.
                        break
                        ;;
                    *)
                        termux_core__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_core__tests__log_error "Invalid argument count $#. The 'termux_core__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_CORE__TESTS__TEST_NAMES_FILTER:-}" ]] && [[ ! "$test_name" =~ $TERMUX_CORE__TESTS__TEST_NAMES_FILTER ]]; then
        return 0
    fi

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

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

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

    termux_core__tests__log 5 "expected_exit_code='$expected_exit_code'"
    termux_core__tests__log 5 "expected_output_regex='$expected_output_regex'"

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

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

    output="$(printf "%s" "$test_file_content" > "$TERMUX_CORE__TESTS__SCRIPT_TEST_FILE_PATH" 2>&1)"
    return_value=$?
    if [ $return_value -ne 0 ]; then
        printf "%s\n" "$output" 1>&2
        termux_core__tests__log_error "Failed to create the '$TERMUX_CORE__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_CORE__TESTS__SCRIPT_TEST_FILE_PATH" 2>&1)"
        return_value=$?
        if [ $return_value -ne 0 ]; then
            printf "%s\n" "$output" 1>&2
            termux_core__tests__log_error "Failed to set the executable bit for the '$TERMUX_CORE__TESTS__SCRIPT_TEST_FILE_PATH' file for the '$test_name' test"
            return $return_value
        fi
    fi

    actual_output="$(cd "$working_directory" && "${execution_path:-$TERMUX_CORE__TESTS__SCRIPT_TEST_FILE_PATH}" "$@" 2>&1)"
    actual_exit_code=$?
    if [[ -n "$expected_output_regex" ]] && [[ ! "$actual_output" =~ $expected_output_regex ]]; then
        termux_core__tests__log_error "FAILED: '$test_name' test"
        termux_core__tests__log_error "Expected output_regex does not equal match actual output"
        test_failed="true"
    elif [ $actual_exit_code != "$expected_exit_code" ]; then
        termux_core__tests__log_error "$actual_output"
        termux_core__tests__log_error "FAILED: '$test_name' test"
        termux_core__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_core__tests__log_error "test_file_content=${NL}"'```'"${NL}$test_file_content${NL}"'```'
        else
            termux_core__tests__log_error "test_file_content='$test_file_content'"
        fi
        termux_core__tests__log_error "actual_exit_code: '$actual_exit_code'"
        termux_core__tests__log_error "expected_exit_code: '$expected_exit_code'"
        termux_core__tests__log_error "actual_output: '$actual_output'"
        termux_core__tests__log_error "expected_output_regex: '$expected_output_regex'"
        return 100
    else
        #termux_core__tests__log 2 "PASSED"

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

        if [[ "$TERMUX_CORE__TESTS__LOG_EMPTY_LINE_AFTER_SCRIPT_TEST" == "true" ]]; then
            termux_core__tests__log 5 ""
        fi

        return 0
    fi

}





##
# `get_export_scoped_variable_command` `<output_variable_name>` \
#     `<var_root_scope_name>` `<var_sub_scope_name>` `<var_sub_name>` \
#     `<var_value>`
##
get_export_scoped_variable_command() {

    termux_core__tests__validate_argument_count eq $# 5 get_export_scoped_variable_command "$@" || return $?

    local output_variable_name="$1"
    local var_root_scope_name="$2"
    local var_sub_scope_name="$3"
    local var_sub_name="$4"
    local var_value="$5"

    printf -v "$output_variable_name" "%s" \
        "export ${var_root_scope_name}${var_sub_scope_name}${var_sub_name}='${var_value//\'/\'\\\'\'}';" || return $?

    return 0

}

##
# `append_export_scoped_variable_command` `<output_variable_name>` \
#     `<var_root_scope_name>` `<var_sub_scope_name>` `<var_sub_name>` \
#     `<var_value>`
##
append_export_scoped_variable_command() {

    termux_core__tests__validate_argument_count eq $# 5 append_export_scoped_variable_command "$@" || return $?

    local output_variable_name="$1"
    local var_root_scope_name="$2"
    local var_sub_scope_name="$3"
    local var_sub_name="$4"
    local var_value="$5"

    local __command

    get_export_scoped_variable_command __command "$var_root_scope_name" "$var_sub_scope_name" "$var_sub_name" "$var_value" || return $?

    printf -v "$output_variable_name" "%s" \
        "${!output_variable_name:-}${NL}${__command}" || return $?

    return 0

}



##
# `get_unset_scoped_variable_command` `<output_variable_name>` \
#     `<var_root_scope_name>` `<var_sub_scope_name>` `<var_sub_name>`
##
get_unset_scoped_variable_command() {

    termux_core__tests__validate_argument_count eq $# 4 get_unset_scoped_variable_command "$@" || return $?

    local output_variable_name="$1"
    local var_root_scope_name="$2"
    local var_sub_scope_name="$3"
    local var_sub_name="$4"

    printf -v "$output_variable_name" "%s" \
        "unset ${var_root_scope_name}${var_sub_scope_name}${var_sub_name};" || return $?

    return 0

}

##
# `append_unset_scoped_variable_command` `<output_variable_name>` \
#     `<var_root_scope_name>` `<var_sub_scope_name>` `<var_sub_name>`
##
append_unset_scoped_variable_command() {

    termux_core__tests__validate_argument_count eq $# 4 append_unset_scoped_variable_command "$@" || return $?

    local output_variable_name="$1"
    local var_root_scope_name="$2"
    local var_sub_scope_name="$3"
    local var_sub_name="$4"

    local __command

    get_unset_scoped_variable_command __command "$var_root_scope_name" "$var_sub_scope_name" "$var_sub_name" || return $?

    printf -v "$output_variable_name" "%s" \
        "${!output_variable_name:-}${NL}${__command}" || return $?

    return 0

}





##
# 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_core__tests__source_file_from_path <file_name>
##
termux_core__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_core__tests__copy_variable` `<output_variable_name>` `<input_variable_name>`
##
termux_core__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_core__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_core__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_core__tests__escape_string_for_regex` `<string>`
##
termux_core__tests__escape_string_for_regex() {

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

}





##
# `termux_core__tests__get_arguments_string` `<output_variable_name>` `<arguments...>`
##
termux_core__tests__get_arguments_string() {

    local output_variable_name="${1:-}"
    shift 1

    local argument
    local __arguments_string=""
    local i=1

    while [ $# -ne 0 ]; do
        argument="$1"
        __arguments_string="$__arguments_string$i: \`$argument\`"

        if [ $# -gt 1 ]; then
            __arguments_string="$__arguments_string$NL"
        fi

        i=$((i + 1))
        shift
    done

    printf -v "$output_variable_name" "%s" "$__arguments_string" || return $?

}

##
# `termux_core__tests__print_arguments_string` `<arguments...>`
##
termux_core__tests__print_arguments_string() {

    local arguments_string=""
    termux_core__tests__get_arguments_string arguments_string "$@" || return $?
     printf "%s\n" "$arguments_string"

}

##
# `termux_core__tests__validate_argument_count` `eq`|`lt`|`le`|`gt`|`ge` `<arguments_received_count>` `<arguments_actual>` `<label>` [`<arguments_received>`]
# `termux_core__tests__validate_argument_count` `or` `<arguments_received_count>` `<arguments_actual_1>` `<arguments_actual_2>` `<label>` [`<arguments_received>`]
# `termux_core__tests__validate_argument_count` `between` `<arguments_received_count>` `<arguments_actual_min>` `<arguments_actual_max>` `<label>` [`<arguments_received>`]
##
termux_core__tests__validate_argument_count() {

    local return_value=0

    case "$1" in
        eq) if [ "$2" -eq "$3" ]; then :; else
                local label="$4"; case "$label" in *[!a-zA-Z0-9_-]*) :;; *) label="'$label'";;esac;
                termux_core__tests__log_error "Invalid argument count '$2' to $label. Expected '$3' argument(s)."; shift 4; return_value=1; fi;;
        lt) if [ "$2" -lt "$3" ]; then :; else
                local label="$4"; case "$label" in *[!a-zA-Z0-9_-]*) :;; *) label="'$label'";;esac;
                termux_core__tests__log_error "Invalid argument count '$2' to $label. Expected less than '$3' argument(s)."; shift 4; return_value=1; fi;;
        le) if [ "$2" -le "$3" ]; then :; else
                local label="$4"; case "$label" in *[!a-zA-Z0-9_-]*) :;; *) label="'$label'";;esac;
                termux_core__tests__log_error "Invalid argument count '$2' to $label. Expected less than or equal to '$3' argument(s)."; shift 4; return_value=1; fi;;
        gt) if [ "$2" -gt "$3" ]; then :; else
                local label="$4"; case "$label" in *[!a-zA-Z0-9_-]*) :;; *) label="'$label'";;esac;
                termux_core__tests__log_error "Invalid argument count '$2' to $label. Expected greater than '$3' argument(s)."; shift 4; return_value=1; fi;;
        ge) if [ "$2" -ge "$3" ]; then :; else
                local label="$4"; case "$label" in *[!a-zA-Z0-9_-]*) :;; *) label="'$label'";;esac;
                termux_core__tests__log_error "Invalid argument count '$2' to $label. Expected greater than or equal to '$3' argument(s)."; shift 4; return_value=1; fi;;
        or) if [ "$2" -eq "$3" ] || [ "$2" -eq "$4" ]; then :; else
                local label="$5"; case "$label" in *[!a-zA-Z0-9_-]*) :;; *) label="'$label'";;esac;
                termux_core__tests__log_error "Invalid argument count '$2' to $label. Expected '$3' or '$4' argument(s)."; shift 5; return_value=1; fi;;
        between) if [ "$2" -ge "$3" ] && [ "$2" -le "$4" ]; then :; else
                local label="$5"; case "$label" in *[!a-zA-Z0-9_-]*) :;; *) label="'$label'";;esac;
                termux_core__tests__log_error "Invalid argument count '$2' to $label. Expected between '$3' and '$4' (inclusive) argument(s)."; shift 5; return_value=1; fi;;
        *)
            termux_core__tests__log_error "The comparison '$1' while running 'termux_core__tests__validate_argument_count' is invalid"; return 1;;
    esac

    [ $return_value -eq 0 ] && return 0

    if [ $# -gt 0 ]; then
        termux_core__tests__print_arguments_string "$@" || return $?
    fi

    return $return_value

}



##
# `termux_core__tests__print_elapsed_time` `<start_time>`
##
termux_core__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_core__tests__traps()`.
##
termux_core__tests__set_traps() {

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

    return 0

}

termux_core__tests__traps_killtree() {

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

}

termux_core__tests__traps() {

    local exit_code=$?
    trap - EXIT

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

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

}





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

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

    if [ $# -eq 0 ]; then
        TERMUX_CORE__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_CORE__TESTS__COMMAND_TYPE_NOOP="true"
                        show_help; return $?
                        ;;
                    version)
                        TERMUX_CORE__TESTS__COMMAND_TYPE_NOOP="true"
                        echo "termux-core-tests version=0.3.0 org=termux project=termux-core-package"; return $?
                        ;;
                    quiet)
                        TERMUX_CORE__TESTS__LOG_LEVEL=0
                        ;;
                    no-clean)
                        TERMUX_CORE__TESTS__NO_CLEAN="true"
                        ;;
                    only-termux-core-tests)
                        TERMUX_CORE__TESTS__TEST_PACKAGES_FILTER="^termux-core$"
                        ;;
                    skip-termux-core-env-variable-tests)
                        TERMUX_CORE__TESTS__SKIP_TERMUX_CORE_ENV_VARIABLE_TESTS="true"
                        ;;
                    test-names-filter=?*)
                        TERMUX_CORE__TESTS__TEST_NAMES_FILTER="$opt_arg" || return $?
                        ;;
                    test-names-filter | test-names-filter=)
                        termux_core__tests__log_error "No argument set for arg option '--${OPTARG%=*}'."
                        return 64 # EX__USAGE
                        ;;
                    test-packages-filter=?*)
                        TERMUX_CORE__TESTS__TEST_PACKAGES_FILTER="$opt_arg" || return $?
                        ;;
                    test-packages-filter | test-packages-filter=)
                        termux_core__tests__log_error "No argument set for arg option '--${OPTARG%=*}'."
                        return 64 # EX__USAGE
                        ;;
                    tests-path=?*)
                        TERMUX_CORE__TESTS__TESTS_PATH="$(readlink -f -- "$opt_arg")" || return $?
                        ;;
                    tests-path | tests-path=)
                        termux_core__tests__log_error "No argument set for arg option '--${OPTARG%=*}'."
                        return 64 # EX__USAGE
                        ;;
                    '')
                        # End of options `--`.
                        break
                        ;;
                    *)
                        termux_core__tests__log_error "Unknown option: '--${OPTARG:-}'."
                        return 64 # EX__USAGE
                        ;;
                esac
                ;;
            h)
                TERMUX_CORE__TESTS__COMMAND_TYPE_NOOP="true"
                show_help; return $?
                ;;
            q)
                TERMUX_CORE__TESTS__LOG_LEVEL=0
                ;;
            v)
                if [ "$TERMUX_CORE__TESTS__LOG_LEVEL" -lt "$TERMUX_CORE__TESTS__MAX_LOG_LEVEL" ]; then
                    TERMUX_CORE__TESTS__LOG_LEVEL=$((TERMUX_CORE__TESTS__LOG_LEVEL+1));
                else
                    termux_core__tests__log_error "Invalid option, max log level is $TERMUX_CORE__TESTS__MAX_LOG_LEVEL."
                    return 64 # EX__USAGE
                fi
                ;;
            f)
                TERMUX_CORE__TESTS__USE_FSANITIZE_BUILDS="true"
                ;;
            l)
                TERMUX_CORE__TESTS__DETECT_LEAKS=1
                ;;
            \?)
                :;;
        esac
    done
    shift $((OPTIND - 1)) # Remove already processed arguments from argument list

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

    TERMUX_CORE__TESTS__COMMAND_TYPE_ID="$1"

    return 0;

}

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

    cat <<'HELP_EOF'
termux-core-tests can be used to run tests for 'termux-core' and
other external Termux packages.


Usage:
    termux-core-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.
    [ --no-clean ]            Do not clean test files on failure.
    [ --only-termux-core-tests ]
                              Only run 'termux-core' package tests.
    [ --skip-termux-core-env-variable-tests ]
                              Skip 'termux-core' package tests for
                              Termux scoped environment variables.
    [ --test-names-filter=<filter> ]
                              Regex to filter which tests to run by
                              test name.
    [ --test-packages-filter=<filter> ]
                              Regex to filter which tests to run by
                              package names. By default tests for
                              both 'termux-core' and external Termux
                              packages are run.
    [ --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_core__tests__main "$@"
    exit $?
fi
