#
# Testing CamiTK python module (defined as ${PYCAMITK_LIB_NAME})
#
# Testing CamiTK python module is done in an automatically generated python environment (in directory VENV_DIR).
# Tests are written in the PYCAMITK_TEST_SCRIPT.
# All python packages required by the tests must be declared in TEST_REQUIREMENTS and are automatically
# installed in the virtual environment. They will all be labeled PYTEST_TARGET+test-name
#
# This is done in 3 steps:
# 1. use python to create a virtual env
# 2. use PYTEST_DISCOVER_TESTS_SCRIPT to generate CTEST_GENERATED_FILE, a CMake file that contains the list of ctests to add
# 3. include CTEST_GENERATED_FILE in the CMake current configuration
#
# PYTEST_DISCOVER_TESTS_SCRIPT read the PYCAMITK_TEST_SCRIPT using ast (abstract syntax tree) package to list all the
# function that start with "test_" (that is all the functions used by pytest to generate a test). It then add one ctest
# for each pytest.
#
# Note:
# - if you modify PYCAMITK_TEST_SCRIPT or PYTEST_DISCOVER_TESTS_SCRIPT, CMake will detect it automatically and reconfigure the project
# - if you modify TEST_REQUIREMENTS, CMake will detect it automatically and rerun pip install -r ${TEST_REQUIREMENTS}
#
# Warning:
# VENV_DIR, PYTHON_EXTRA_PATH and PYTEST_TARGET variables are required by the CTEST_GENERATED_FILE (see PYTEST_DISCOVER_TESTS_SCRIPT source):
# - VENV_DIR is used to determine the python to use (the venv should have pytest installed)
# - PYTHON_EXTRA_PATH is used to determine were the python module to be tested is found
# - PYTEST_TARGET is used to add a label in order for ctest/CI to associate the test to the given target

# Required variables
set(VENV_DIR "${CMAKE_CURRENT_BINARY_DIR}/.venv")
set(TEST_REQUIREMENTS "${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt")
set(PYTEST_DISCOVER_TESTS_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/pytest_discover_tests.py")
set(PYTEST_TARGET "${PYCAMITK_TARGET_LIB_NAME}") # to associate the test with the target
set(PYTHON_EXTRA_PATH "${OUTPUT_DIRECTORY}") # where camik python module shared object is built
set(PYCAMITK_TEST_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/camitk_python_test.py")
set(CTEST_GENERATED_FILE "${CMAKE_CURRENT_BINARY_DIR}/generated_pytests.cmake")
if(WIN32)
    set(VENV_EXECUTABLE_DIR "${VENV_DIR}/Scripts")
    # On Windows, add ${CAMITK_BUILD_PRIVATE_LIB_DIR} and  ${CAMITK_BUILD_BIN_DIR} to PATH in order 
    # to find the public and private library dll
    if (MSVC)
        # MSVC requires path to lib for the DLL
        string(REGEX REPLACE "/" "\\\\" PATH_TO_DLL "${CAMITK_BUILD_PRIVATE_LIB_DIR};${CAMITK_BUILD_BIN_DIR}")
        # all ; needs to be escaped as well...
        # see https://developercommunity.visualstudio.com/t/cmake-tests-environment-variables-ignored/210188#T-N931342
        string(REPLACE ";" "\\;" ESCAPED_PATH "$ENV{PATH};${PATH_TO_DLL}") 

        # file(TO_NATIVE_PATH "${PYTHON_EXTRA_PATH}" PYTHON_EXTRA_PATH)
        string(REGEX REPLACE "/" "\\\\" PYTHON_EXTRA_PATH "${PYTHON_EXTRA_PATH}")
        message(STATUS "On windows: PYTHON_EXTRA_PATH=${PYTHON_EXTRA_PATH}")
    endif()
else()
    set(VENV_EXECUTABLE_DIR "${VENV_DIR}/bin")
    unset(ESCAPED_PATH)
endif()

#
# 1. create python virtual environment for testing
#
if (NOT EXISTS ${VENV_DIR})
    message(STATUS "Creating python virtual environment for CamiTK python module tests using python '${Python_EXECUTABLE}' in directory ${VENV_DIR}...")
    execute_process(COMMAND "${Python_EXECUTABLE}" "-m" "venv" "${VENV_DIR}" 
                    RESULT_VARIABLE PYTHON_CREATE_VENV_RETURN_CODE
                    OUTPUT_VARIABLE PYTHON_CREATE_VENV_OUTPUT
                    ERROR_VARIABLE  PYTHON_CREATE_VENV_OUTPUT
                    OUTPUT_STRIP_TRAILING_WHITESPACE
                    ERROR_STRIP_TRAILING_WHITESPACE)
    if(PYTHON_CREATE_VENV_RETURN_CODE)
        message(FATAL_ERROR "Failed to create python virtual environment:\nreturn code:\n${PYTHON_CREATE_VENV_RETURN_CODE}\noutput:\n${PYTHON_CREATE_VENV_OUTPUT}")
    else()
        message(STATUS "Python virtual environment for CamiTK python module tests created in ${VENV_DIR}")
    endif()

    message(STATUS "Installing CamiTK python module tests requirements...")
    execute_process(COMMAND "${VENV_EXECUTABLE_DIR}/pip" "install" "-r" "${TEST_REQUIREMENTS}"
                    WORKING_DIRECTORY ${VENV_DIR}
                    RESULT_VARIABLE PYTHON_INSTALL_REQUIREMENTS_RETURN_CODE
                    OUTPUT_VARIABLE PYTHON_INSTALL_REQUIREMENTS_OUTPUT
                    ERROR_VARIABLE  PYTHON_INSTALL_REQUIREMENTS_OUTPUT
                    OUTPUT_STRIP_TRAILING_WHITESPACE
                    ERROR_STRIP_TRAILING_WHITESPACE)
    if(PYTHON_INSTALL_REQUIREMENTS_RETURN_CODE)
        message(FATAL_ERROR "Failed to install requirements for CamiTK python module tests:\nreturn code:\n${PYTHON_INSTALL_REQUIREMENTS_RETURN_CODE}\noutput:\n${PYTHON_INSTALL_REQUIREMENTS_OUTPUT}")
    else()
        message(STATUS "Python virtual environment for CamiTK python module tests ready.")
    endif()
endif()

# reinstall dependencies if requirements.txt changes
add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/requirements-last-updated.txt
    COMMAND "${VENV_EXECUTABLE_DIR}/pip" "install" "-r" "${TEST_REQUIREMENTS}"
    COMMAND ${CMAKE_COMMAND} -E echo "Pip install executed" > ${CMAKE_CURRENT_BINARY_DIR}/requirements-last-updated.txt
    DEPENDS ${TEST_REQUIREMENTS}
    COMMENT "Updating CamiTK python module test dependencies because ${TEST_REQUIREMENTS} has changed"
    VERBATIM
)
add_custom_target(
    ${PYCAMITK_TARGET_LIB_NAME}-test-requirements ALL
    COMMENT "Update CamiTK python module test requirements when necessary"
    DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/requirements-last-updated.txt
)

#
# 2. Create the list of tests from PYCAMITK_TEST_FILE (at configure time)
#
execute_process(COMMAND "${VENV_EXECUTABLE_DIR}/python" ${PYTEST_DISCOVER_TESTS_SCRIPT} ${PYCAMITK_TEST_SCRIPT} "${CTEST_GENERATED_FILE}" "${PYTHON_EXTRA_PATH}" "${ESCAPED_PATH}"
                    WORKING_DIRECTORY ${VENV_DIR}
                    RESULT_VARIABLE PYTHON_GENERATE_TESTS_RETURN_CODE
                    OUTPUT_VARIABLE PYTHON_GENERATE_TESTS_OUTPUT
                    ERROR_VARIABLE  PYTHON_GENERATE_TESTS_OUTPUT
                    OUTPUT_STRIP_TRAILING_WHITESPACE
                    ERROR_STRIP_TRAILING_WHITESPACE)
if(PYTHON_GENERATE_TESTS_RETURN_CODE)
    message(FATAL_ERROR "Failed to generate tests from pytest:\nreturn code:\n${PYTHON_GENERATE_TESTS_RETURN_CODE}\noutput:\n${PYTHON_GENERATE_TESTS_OUTPUT}")
else()
    message(STATUS "Python test generated from pytest using PYTHON_EXTRA_PATH=${PYTHON_EXTRA_PATH}.")
endif()

set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
  "${PYTEST_DISCOVER_TESTS_SCRIPT}"
  "${PYCAMITK_TEST_SCRIPT}"
)

#
# 3. Include the generated tests
#
include("${CTEST_GENERATED_FILE}")