#!/usr/bin/env bash
# --------------------( LICENSE                            )--------------------
# Copyright (c) 2014-2024 Beartype authors.
# See "LICENSE" for further details.
#
# --------------------( SYNOPSIS                           )--------------------
# Bash shell script wrapping this project's pytest-based test suite, passing
# sane default options suitable for interactive terminal testing and otherwise
# passing all passed arguments as is to the "pytest" command.
#
# This script is defined as a Bash rather than Bourne script purely for the
# canonical ${BASH_SOURCE} string global, reliably providing the absolute
# pathnames of this script and hence this script's directory.
#
# --------------------( CAVEATS                            )--------------------
# *THE HIGHER-LEVEL "tox" SCRIPT SHOULD TYPICALLY BE RUN INSTEAD.* This
# lower-level script only exercises this project against the single Python
# interpreter associated with the "pytest" command and is thus suitable *ONLY*
# as a rapid sanity check. Meanwhile, the higher-level "tox" command exercises
# this project against all installed Python interpreters and is thus suitable
# as a full-blown correctness check (e.g., before submitting pull requests).

# ....................{ PREAMBLE                           }....................
# Enable strictness for sanity.
set -e

# ....................{ ARRAYS                             }....................
# Array of all shell words with which to invoke Python. Dismantled, this is:
# * "-X dev", enabling the Python Development Mode (PDM). See also commentary
#   for the ${PYTHONDEVMODE} shell variable in the "tox.ini" file.
PYTHON_ARGS=( command python3 -X dev )
# PYTHON_ARGS=( command python3.8 -X dev )
# PYTHON_ARGS=( command python3.9 -X dev )
# PYTHON_ARGS=( command python3.10 -X dev )
# PYTHON_ARGS=( command python3.11 -X dev )
# PYTHON_ARGS=( command python3.12 -X dev )
# PYTHON_ARGS=( command pypy3.7 -X dev )

# Array of all shell words to be passed to "python3" below.
PYTEST_ARGS=(
    pytest

    # Enable colour output to ensure colour under piped pagers (e.g., "less").
    '--color=yes'

    # Halt testing on the first failure for interactive tests. Permitting
    # multiple failures complicates failure output, especially when every
    # failure after the first is a result of the same underlying issue. When
    # testing non-interactively, testing is typically *NOT* halted on the first
    # failure. Hence, this option is confined to this script rather than added
    # to our general-purpose "pytest.ini" configuration.
    '--maxfail=1'

    # Pass all remaining arguments to "pytest" as is.
    "${@}"
)
# echo "pytest args: ${PYTEST_ARGS[*]}"

# ....................{ FUNCTIONS                          }....................
# is_package(module_name: str) -> bool
#
# Report success only if a package or module with the passed fully-qualified
# name is importable and thus installed under the active Python interpreter.
# This tester is strongly inspired by this StackOverflow post:
#     https://askubuntu.com/a/588392/415719
function is_package() {
    # Validate and localize all passed arguments.
    (( $# == 1 )) || {
        echo 'Expected exactly one argument.' 1>&2
        return 1
    }
    local package_name="${1}"

    # Report success only if this package or module exists.
    "${PYTHON_ARGS[@]}" -c "import ${package_name}" 2>/dev/null
}


# str canonicalize_path(str pathname)
#
# Canonicalize the passed pathname.
function canonicalize_path() {
    # Validate and localize all passed arguments.
    (( $# == 1 )) || {
        echo 'Expected exactly one argument.' 1>&2
        return 1
    }
    local pathname="${1}"

    # The "readlink" command's GNU-specific "-f" option would be preferable but
    # is unsupported by macOS's NetBSD-specific version of "readlink". Instead,
    # just defer to Python for portability.
    command python3 -c "
import os, sys
print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${pathname}"
}

# ....................{ PATHS                              }....................
# Absolute or relative filename of this script.
SCRIPT_FILENAME="$(canonicalize_path "${BASH_SOURCE[0]}")"

# Absolute or relative dirname of the directory directly containing this
# script, equivalent to the top-level directory for this project.
SCRIPT_DIRNAME="$(dirname "${SCRIPT_FILENAME}")"

# ....................{ MAIN                               }....................
# Temporarily change the current working directory to that of this project.
pushd "${SCRIPT_DIRNAME}" >/dev/null

# If the third-party "coverage" package is installed under the desired Python
# interpreter *AND* the "-k" option was *NOT* passed, then measure coverage
# while running tests.
#
# If the "-k" option was passed, we avoid measuring coverage. Why? Because that
# option restricts testing to a subset of tests, guaranteeing that coverage
# measurements will be misleading at best and trigger test failure at worst
# (e.g., if the "fail_under" option is enabled in ".coveragerc").
if is_package coverage && [[ ! " ${PYTEST_ARGS[*]} " =~ " -k " ]]; then
    # If run this project's pytest-based test suite with all passed arguments
    # (while measuring coverage) succeeds, generate a terminal coverage report.
    #
    # Note that the last argument passed to "pytest" *MUST* be ".". Why?
    # Because "." notifies pytest of the relative dirname of the root directory
    # for this project. On startup, pytest internally:
    # * Sets its "rootdir" property to this dirname in absolute form.
    # * Sets its "inifile" property to the concatenation of this dirname
    #   with the basename "pytest.ini" if that top-level configuration file
    #   exists.
    # * Prints the initial values of these properties to stdout.
    #
    # *THIS IS ESSENTIAL.* If *NOT* explicitly passed this dirname as an
    # argument, pytest may fail to set these properties to the expected
    # pathnames. For unknown reasons (presumably unresolved pytest issues),
    # pytest instead sets "rootdir" to the absolute dirname of the current
    # user's home directory and "inifile" to "None". Since no user's home
    # directory contains a "pytest.ini" file, pytest then prints errors ala:
    #    $ ./pytest -k test_sim_export --export-sim-conf-dir ~/tmp/yolo
    #    running test
    #    Running py.test with arguments: ['--capture=no', '--maxfail=1', '-k', 'test_sim_export', '--export-sim-conf-dir', '/home/leycec/tmp/yolo']
    #    usage: setup.py [options] [file_or_dir] [file_or_dir] [...]
    #    setup.py: error: unrecognized arguments: --export-sim-conf-dir
    #      inifile: None
    #      rootdir: /home/leycec
    #
    # See the following official documentation for further details, entitled
    # "Initialization: determining rootdir and inifile":
    #     https://docs.pytest.org/en/latest/customize.html
    "${PYTHON_ARGS[@]}" -m coverage run -m "${PYTEST_ARGS[@]}" . &&
    "${PYTHON_ARGS[@]}" -m coverage report
# Else, run this project's pytest-based test suite with all passed arguments
# *WITHOUT* measuring coverage.
else
    "${PYTHON_ARGS[@]}" -m "${PYTEST_ARGS[@]}" .
fi

# 0-based exit code reported by the prior command.
exit_code=$?

# Revert the current working directory to the prior such directory.
popd >/dev/null

# Report the same exit code from this script.
exit ${exit_code}
