# Running various test suites against LuaJIT.

include(MakeLuaPath)

find_program(LUACHECK luacheck)
if(LUACHECK)
  # XXX: The tweak below relates to luacheck problem with paths.
  # If the working directory or one used in luacheck options is
  # not a real path, luacheck doesn't handle it the right way.
  # Hence the paths used by luacheck in CMake ought to be resolved
  # to the real ones. For more info, see the following issue.
  # https://github.com/mpeterv/luacheck/issues/208
  get_filename_component(LUACHECK_SOURCE_DIR "${PROJECT_SOURCE_DIR}" REALPATH)
  get_filename_component(LUACHECK_BINARY_DIR "${PROJECT_BINARY_DIR}" REALPATH)
  set(LUACHECK_RC ${LUACHECK_SOURCE_DIR}/.luacheckrc)
  file(GLOB_RECURSE LUACHECK_DEPS ${LUACHECK_SOURCE_DIR}/*.lua)
  add_custom_target(${PROJECT_NAME}-luacheck
    DEPENDS ${LUACHECK_RC} ${LUACHECK_DEPS}
  )
  add_custom_command(TARGET ${PROJECT_NAME}-luacheck
    COMMENT "Running luacheck static analysis"
    COMMAND
      ${LUACHECK} ${LUACHECK_SOURCE_DIR}
        --codes
        --config ${LUACHECK_RC}
        # XXX: jit/vmdef.lua is an autogenerated Lua source, so
        # there is no need to run luacheck against it. Hence
        # explicitly exclude this file from the list for check.
        --exclude-files ${LUACHECK_BINARY_DIR}/src/jit/vmdef.lua
    # XXX: Filenames in .luacheckrc are considered relative to
    # the working directory, hence luacheck should be run in the
    # project root directory.
    WORKING_DIRECTORY ${LUACHECK_SOURCE_DIR}
  )
else()
  add_custom_target(${PROJECT_NAME}-luacheck)
  add_custom_command(TARGET ${PROJECT_NAME}-luacheck
    COMMENT "`luacheck' is not found, so ${PROJECT_NAME}-luacheck target is dummy"
  )
endif()

find_program(FLAKE8 flake8)
if(FLAKE8)
  get_filename_component(FLAKE8_SOURCE_DIR "${PROJECT_SOURCE_DIR}" REALPATH)
  set(FLAKE8_RC ${FLAKE8_SOURCE_DIR}/.flake8rc)
  file(GLOB_RECURSE FLAKE8_DEPS ${FLAKE8_SOURCE_DIR}/*.py)
  add_custom_target(${PROJECT_NAME}-flake8
    DEPENDS ${FLAKE8_DEPS}
  )
  add_custom_command(TARGET ${PROJECT_NAME}-flake8
    COMMENT "Running flake8 static analysis"
    COMMAND
      ${FLAKE8} ${FLAKE8_DEPS}
        --config ${FLAKE8_RC}
        --jobs ${CMAKE_BUILD_PARALLEL_LEVEL}
    WORKING_DIRECTORY ${FLAKE8_SOURCE_DIR}
  )
else()
  add_custom_target(${PROJECT_NAME}-flake8)
  add_custom_command(TARGET ${PROJECT_NAME}-flake8
    COMMENT "`flake8' is not found, so ${PROJECT_NAME}-flake8 target is dummy"
  )
endif()

add_custom_target(${PROJECT_NAME}-lint DEPENDS
  ${PROJECT_NAME}-luacheck
  ${PROJECT_NAME}-flake8
  ${PROJECT_NAME}-codespell
)

set(LUAJIT_TEST_COMMAND "${LUAJIT_TEST_BINARY} -e dofile[[${LUAJIT_TEST_INIT}]]")

if(LUAJIT_USE_VALGRIND)
  find_program(VALGRIND valgrind)
  if(NOT VALGRIND)
    message(FATAL_ERROR "`valgrind' not found when LUAJIT_USE_VALGRIND option "
                        "is enabled.")
  endif()

  if(NOT LUAJIT_USE_SYSMALLOC)
    message(WARNING
      "LUAJIT_USE_SYSMALLOC option is mandatory for Valgrind's memcheck tool"
      " on x64 and the only way to get useful results from it for all other"
      " architectures.")
  endif()

  list(APPEND LUAJIT_TEST_VALGRIND_SUPP
    --suppressions=${LUAJIT_SOURCE_DIR}/lj.supp
    --suppressions=${LUAJIT_SOURCE_DIR}/lj_extra.supp
  )
  # When set to the default value (zero), the return value from
  # Valgrind will always be the return value of the process being
  # simulated. Set the exit code to non-zero to automatically
  # detect failing tests.
  set(LUAJIT_TEST_VALGRIND_COMMAND
      ${VALGRIND} --error-exitcode=1 ${LUAJIT_TEST_VALGRIND_SUPP})
  set(LUAJIT_TEST_COMMAND
      "${LUAJIT_TEST_VALGRIND_COMMAND} ${LUAJIT_TEST_COMMAND}")
endif()

separate_arguments(LUAJIT_TEST_COMMAND)

set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(AddTestLib)
include(GetLibCVersion)
include(GetLinuxDistro)
include(LibRealPath)

# CTEST_FLAGS is used by CMake targets in test suites.
# XXX: CMake 3.17 introduces CMAKE_CTEST_ARGUMENTS that contains
# CTest options and is used by `test` target.
set(CTEST_FLAGS
  --output-on-failure
  --schedule-random
  # Timeout in seconds. Most LuaJIT tests are fast. However,
  # some tests can hang or execute infinite loop.
  # See <tarantool-c-tests/gh-8594-sysprof-ffunc-crash.test.c>,
  # commit caa99865c206 ("test: rewrite sysprof test using managed execution").
  # Moreover, tests for instrumented LuaJIT require more time.
  # 30 minutes is a sane timeout.
  --timeout 1800
  --parallel ${CMAKE_BUILD_PARALLEL_LEVEL}
)
if(CMAKE_VERBOSE_MAKEFILE)
  list(APPEND CTEST_FLAGS --verbose)
endif()

# It is not possible to add dependencies to `add_test()`
# in CMake, see [1]. CMake 3.7 introduces FIXTURES_REQUIRED [2]
# and FIXTURES_SETUP [3], but these test properties cannot be
# used - this feature is unsupported in the current CMake version.
# To workaround this, the function `add_test_suite_target` is
# introduced. It adds a CMake target that builds testsuite's
# prerequisites and CMake test that executes that target.
#
# 1. https://gitlab.kitware.com/cmake/cmake/-/issues/8774
# 2. https://cmake.org/cmake/help/latest/prop_test/FIXTURES_REQUIRED.html
# 3. https://cmake.org/cmake/help/latest/prop_test/FIXTURES_SETUP.html
function(add_test_suite_target target)
  set(prefix ARG)
  set(noValues)
  set(singleValues LABELS)
  set(multiValues DEPENDS)

  # FIXME: if we update to CMake >= 3.5, can remove this line.
  include(CMakeParseArguments)
  cmake_parse_arguments(${prefix}
                        "${noValues}"
                        "${singleValues}"
                        "${multiValues}"
                        ${ARGN})

  set(_DEPS_TARGET ${target}-deps)

  add_custom_target(${_DEPS_TARGET} DEPENDS ${ARG_DEPENDS})

  add_test(NAME ${_DEPS_TARGET}
    COMMAND ${CMAKE_COMMAND}
            --build ${CMAKE_BINARY_DIR}
            --target ${_DEPS_TARGET}
  )
  set_tests_properties(${_DEPS_TARGET} PROPERTIES
    LABELS ${ARG_LABELS}
  )

  message(STATUS "Add test suite ${ARG_LABELS}")

  add_custom_target(${target}
    COMMAND ${CMAKE_CTEST_COMMAND} -L ${ARG_LABELS} ${CTEST_FLAGS}
    DEPENDS ${_DEPS_TARGET}
  )
endfunction()

add_subdirectory(LuaJIT-tests)
add_subdirectory(PUC-Rio-Lua-5.1-tests)
add_subdirectory(lua-Harness-tests)
add_subdirectory(tarantool-c-tests)
add_subdirectory(tarantool-tests)

# Each testsuite has its own CMake target, but combining these
# target into a single one is not desired, because each target
# runs it's own `ctest` command, which each time enumerates tests
# from zero and prints the test summary at the end of the test run.
# For a common target this output looks inconvenient.
# Therefore, target below executes a single instance of `ctest`
# command that runs all generated CMake tests.
add_custom_target(${PROJECT_NAME}-test
  COMMAND ${CMAKE_CTEST_COMMAND} ${CTEST_FLAGS}
  DEPENDS tarantool-c-tests-deps
          tarantool-tests-deps
          lua-Harness-tests-deps
          PUC-Rio-Lua-5.1-tests-deps
          LuaJIT-tests-deps
)

add_custom_target(${PROJECT_NAME}-check-all
  DEPENDS ${PROJECT_NAME}-test
          ${PROJECT_NAME}-lint
)
