# -*- cmake -*-
include(00-Common)
include(LLTestCommand)
include(GoogleMock)
include(bugsplat)
include(Tut)

#*****************************************************************************
#   LL_ADD_PROJECT_UNIT_TESTS
#*****************************************************************************
MACRO(LL_ADD_PROJECT_UNIT_TESTS project sources)
  # Given a project name and a list of sourcefiles (with optional properties on each),
  # add targets to build and run the tests specified.
  # ASSUMPTIONS:
  # * this macro is being executed in the project file that is passed in
  # * current working SOURCE dir is that project dir
  # * there is a subfolder tests/ with test code corresponding to the filenames passed in
  # * properties for each sourcefile passed in indicate what libs to link that file with (MAKE NO ASSUMPTIONS ASIDE FROM TUT)
  #
  # More info and examples at: https://wiki.secondlife.com/wiki/How_to_add_unit_tests_to_indra_code

  # This here looks weird, but is needed. It will inject GoogleMock into projects that forgot to include `this` (LLAddBuildTest.cmake)
  # But through some other means have access to this macro
  include(GoogleMock)

  if(LL_TEST_VERBOSE)
    message("LL_ADD_PROJECT_UNIT_TESTS UNITTEST_PROJECT_${project} sources: ${sources}")
  endif()

  # Start with the header and project-wide setup before making targets
  #project(UNITTEST_PROJECT_${project})
  # Setup includes, paths, etc
  set(alltest_SOURCE_FILES
          ${CMAKE_SOURCE_DIR}/test/test.cpp
          ${CMAKE_SOURCE_DIR}/test/lltut.cpp
          )
  set(alltest_DEP_TARGETS
          # needed by the test harness itself
          llcommon
          )

  set(alltest_LIBRARIES
          llcommon
          ll::googlemock
          )
  if(NOT "${project}" STREQUAL "llmath")
    # add llmath as a dep unless the tested module *is* llmath!
    list(APPEND alltest_DEP_TARGETS llmath)
    list(APPEND alltest_LIBRARIES llmath )
  endif()

  # Headers, for convenience in targets.
  set(alltest_HEADER_FILES ${CMAKE_SOURCE_DIR}/test/test.h)

  # start the source test executable definitions
  set(${project}_TEST_OUTPUT "")
  foreach (source ${sources})
    string( REGEX REPLACE "(.*)\\.[^.]+$" "\\1" name ${source} )
    string( REGEX REPLACE ".*\\.([^.]+)$" "\\1" extension ${source} )
    if(LL_TEST_VERBOSE)
      message("LL_ADD_PROJECT_UNIT_TESTS UNITTEST_PROJECT_${project} individual source: ${source} (${name}.${extension})")
    endif()

    #
    # Per-codefile additional / external source, header, and include dir property extraction
    #
    # Source
    GET_OPT_SOURCE_FILE_PROPERTY(${name}_test_additional_SOURCE_FILES ${source} LL_TEST_ADDITIONAL_SOURCE_FILES)
    set(${name}_test_SOURCE_FILES
            ${source}
            tests/${name}_test.${extension}
            ${alltest_SOURCE_FILES}
            ${${name}_test_additional_SOURCE_FILES} )
    if(LL_TEST_VERBOSE)
      message("LL_ADD_PROJECT_UNIT_TESTS ${name}_test_SOURCE_FILES ${${name}_test_SOURCE_FILES}")
    endif()

    # Headers
    GET_OPT_SOURCE_FILE_PROPERTY(${name}_test_additional_HEADER_FILES ${source} LL_TEST_ADDITIONAL_HEADER_FILES)
    set(${name}_test_HEADER_FILES ${name}.h ${${name}_test_additional_HEADER_FILES})
    list(APPEND ${name}_test_SOURCE_FILES ${${name}_test_HEADER_FILES})
    if(LL_TEST_VERBOSE)
      message("LL_ADD_PROJECT_UNIT_TESTS ${name}_test_HEADER_FILES ${${name}_test_HEADER_FILES}")
    endif()

    # Setup target
    add_executable(PROJECT_${project}_TEST_${name} ${${name}_test_SOURCE_FILES})

    # Cannot declare a dependency on ${project} because the executable create above will later declare
    # add_dependencies( ${project} ${project}_tests)
    # as such grab ${project}'s interface include dirs and inject them here
    get_property( ${name}_test_additional_INCLUDE_DIRS TARGET ${project} PROPERTY INTERFACE_INCLUDE_DIRECTORIES )
    target_include_directories (PROJECT_${project}_TEST_${name} PRIVATE ${${name}_test_additional_INCLUDE_DIRS} )

    GET_OPT_SOURCE_FILE_PROPERTY(${name}_test_additional_INCLUDE_DIRS ${source} LL_TEST_ADDITIONAL_INCLUDE_DIRS)
    target_include_directories (PROJECT_${project}_TEST_${name} PRIVATE ${${name}_test_additional_INCLUDE_DIRS} )

    target_include_directories (PROJECT_${project}_TEST_${name} PRIVATE ${LIBS_OPEN_DIR}/test )

    set_target_properties(PROJECT_${project}_TEST_${name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${EXE_STAGING_DIR}")

    #
    # Per-codefile additional / external project dep and lib dep property extraction
    #
    # WARNING: it's REALLY IMPORTANT to not mix these. I guarantee it will not work in the future. + poppy 2009-04-19
    # Projects
    GET_OPT_SOURCE_FILE_PROPERTY(${name}_test_additional_PROJECTS ${source} LL_TEST_ADDITIONAL_PROJECTS)
    # Libraries
    GET_OPT_SOURCE_FILE_PROPERTY(${name}_test_additional_LIBRARIES ${source} LL_TEST_ADDITIONAL_LIBRARIES)

    if(LL_TEST_VERBOSE)
      message("LL_ADD_PROJECT_UNIT_TESTS ${name}_test_additional_PROJECTS ${${name}_test_additional_PROJECTS}")
      message("LL_ADD_PROJECT_UNIT_TESTS ${name}_test_additional_LIBRARIES ${${name}_test_additional_LIBRARIES}")
    endif()

    # Add to project
    target_link_libraries(PROJECT_${project}_TEST_${name} ${alltest_LIBRARIES} ${${name}_test_additional_PROJECTS} ${${name}_test_additional_LIBRARIES} )
    add_dependencies( PROJECT_${project}_TEST_${name} ${alltest_DEP_TARGETS})
    # Compile-time Definitions
    GET_OPT_SOURCE_FILE_PROPERTY(${name}_test_additional_CFLAGS ${source} LL_TEST_ADDITIONAL_CFLAGS)
    set_target_properties(PROJECT_${project}_TEST_${name}
            PROPERTIES
            COMPILE_FLAGS "${${name}_test_additional_CFLAGS}"
            COMPILE_DEFINITIONS "LL_TEST=${name};LL_TEST_${name}")
    if(LL_TEST_VERBOSE)
      message("LL_ADD_PROJECT_UNIT_TESTS ${name}_test_additional_CFLAGS ${${name}_test_additional_CFLAGS}")
    endif()

    if (DARWIN)
      # test binaries always need to be signed for local development
      set_target_properties(PROJECT_${project}_TEST_${name}
          PROPERTIES
              XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-")
    endif ()

    #
    # Setup test targets
    #
    set(TEST_EXE $<TARGET_FILE:PROJECT_${project}_TEST_${name}>)
    set(TEST_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/PROJECT_${project}_TEST_${name}_ok.txt)
    set(TEST_CMD ${TEST_EXE} --touch=${TEST_OUTPUT} --sourcedir=${CMAKE_CURRENT_SOURCE_DIR})

    # daveh - what configuration does this use? Debug? it's cmake-time, not build time. + poppy 2009-04-19
    if(LL_TEST_VERBOSE)
      message(STATUS "LL_ADD_PROJECT_UNIT_TESTS ${name} test_cmd  = ${TEST_CMD}")
    endif()

    SET_TEST_PATH(LD_LIBRARY_PATH)
    LL_TEST_COMMAND(TEST_SCRIPT_CMD "${LD_LIBRARY_PATH}" ${TEST_CMD})
    if(LL_TEST_VERBOSE)
      message(STATUS "LL_ADD_PROJECT_UNIT_TESTS ${name} test_script  = ${TEST_SCRIPT_CMD}")
    endif()

    # Add test
    add_custom_command(
            OUTPUT ${TEST_OUTPUT}
            COMMAND ${TEST_SCRIPT_CMD}
            DEPENDS PROJECT_${project}_TEST_${name}
            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
    )
    # Why not add custom target and add POST_BUILD command?
    # Slightly less uncertain behavior
    # (OUTPUT commands run non-deterministically AFAIK) + poppy 2009-04-19
    # > I did not use a post build step as I could not make it notify of a
    # > failure after the first time you build and fail a test. - daveh 2009-04-20
    list(APPEND ${project}_TEST_OUTPUT ${TEST_OUTPUT})
  endforeach (source)

  # Add the test runner target per-project
  # (replaces old _test_ok targets all over the place)
  add_custom_target(${project}_tests ALL DEPENDS ${${project}_TEST_OUTPUT})
  add_dependencies(${project} ${project}_tests)
ENDMACRO(LL_ADD_PROJECT_UNIT_TESTS)

#*****************************************************************************
#   GET_OPT_SOURCE_FILE_PROPERTY
#*****************************************************************************
MACRO(GET_OPT_SOURCE_FILE_PROPERTY var filename property)
  get_source_file_property(${var} "${filename}" "${property}")
  if("${${var}}" MATCHES NOTFOUND)
    set(${var} "")
  endif()
ENDMACRO(GET_OPT_SOURCE_FILE_PROPERTY)

#*****************************************************************************
#   LL_ADD_INTEGRATION_TEST
#*****************************************************************************
FUNCTION(LL_ADD_INTEGRATION_TEST
        testname
        additional_source_files
        library_dependencies
        # variable args
        )
  if(TEST_DEBUG)
    message(STATUS "Adding INTEGRATION_TEST_${testname} - debug output is on")
  endif()

  set(source_files
          tests/${testname}_test.cpp
          ${CMAKE_SOURCE_DIR}/test/test.cpp
          ${CMAKE_SOURCE_DIR}/test/lltut.cpp
          ${additional_source_files}
          )

  set(libraries
          ${library_dependencies}
          ll::googlemock
          )

  # Add test executable build target
  if(TEST_DEBUG)
    message(STATUS "ADD_EXECUTABLE(INTEGRATION_TEST_${testname} ${source_files})")
  endif()

  add_executable(INTEGRATION_TEST_${testname} ${source_files})
  set_target_properties(INTEGRATION_TEST_${testname}
          PROPERTIES
          RUNTIME_OUTPUT_DIRECTORY "${EXE_STAGING_DIR}"
          COMPILE_DEFINITIONS "LL_TEST=${testname};LL_TEST_${testname}"
          )

  # The following was copied to llcorehttp/CMakeLists.txt's texture_load target.
  # Any changes made here should be replicated there.
  if (WINDOWS)
    set_target_properties(INTEGRATION_TEST_${testname}
            PROPERTIES
            LINK_FLAGS "/debug /NODEFAULTLIB:LIBCMT /SUBSYSTEM:CONSOLE"
            )
  endif ()

  if (DARWIN)
    # test binaries always need to be signed for local development
    set_target_properties(INTEGRATION_TEST_${testname}
            PROPERTIES
            XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-")
  endif ()

  # Add link deps to the executable
  if(TEST_DEBUG)
    message(STATUS "TARGET_LINK_LIBRARIES(INTEGRATION_TEST_${testname} ${libraries})")
  endif()

  target_link_libraries(INTEGRATION_TEST_${testname} ${libraries})
  target_include_directories (INTEGRATION_TEST_${testname} PRIVATE ${LIBS_OPEN_DIR}/test )

  # Create the test running command
  set(test_command ${ARGN})
  set(TEST_EXE $<TARGET_FILE:INTEGRATION_TEST_${testname}>)
  list(FIND test_command "{}" test_exe_pos)
  if(test_exe_pos LESS 0)
    # The {} marker means "the full pathname of the test executable."
    # test_exe_pos -1 means we didn't find it -- so append the test executable
    # name to $ARGN, the variable part of the arg list. This is convenient
    # shorthand for both straightforward execution of the test program (empty
    # $ARGN) and for running a "wrapper" program of some kind accepting the
    # pathname of the test program as the last of its args. You need specify
    # {} only if the test program's pathname isn't the last argument in the
    # desired command line.
    list(APPEND test_command "${TEST_EXE}")
  else (test_exe_pos LESS 0)
    # Found {} marker at test_exe_pos. Remove the {}...
    list(REMOVE_AT test_command test_exe_pos)
    # ...and replace it with the actual name of the test executable.
    list(INSERT test_command test_exe_pos "${TEST_EXE}")
  endif()

  SET_TEST_PATH(LD_LIBRARY_PATH)
  LL_TEST_COMMAND(TEST_SCRIPT_CMD "${LD_LIBRARY_PATH}" ${test_command})

  if(TEST_DEBUG)
    message(STATUS "TEST_SCRIPT_CMD: ${TEST_SCRIPT_CMD}")
  endif()

  add_custom_command(
          TARGET INTEGRATION_TEST_${testname}
          POST_BUILD
          COMMAND ${TEST_SCRIPT_CMD}
  )

  # Use CTEST? Not sure how to yet...
  # ADD_TEST(INTEGRATION_TEST_RUNNER_${testname} ${TEST_SCRIPT_CMD})

ENDFUNCTION(LL_ADD_INTEGRATION_TEST)

#*****************************************************************************
#   SET_TEST_PATH
#*****************************************************************************
MACRO(SET_TEST_PATH LISTVAR)
  IF(WINDOWS)
    # We typically build/package only Release variants of third-party
    # libraries, so append the Release staging dir in case the library being
    # sought doesn't have a debug variant.
    set(${LISTVAR} ${SHARED_LIB_STAGING_DIR} ${SHARED_LIB_STAGING_DIR}/Release)
  ELSEIF(DARWIN)
    # We typically build/package only Release variants of third-party
    # libraries, so append the Release staging dir in case the library being
    # sought doesn't have a debug variant.
    set(${LISTVAR} ${SHARED_LIB_STAGING_DIR} ${SHARED_LIB_STAGING_DIR}/Release/Resources /usr/lib)
  ELSE(WINDOWS)
    # Linux uses a single staging directory anyway.
    set(${LISTVAR} ${SHARED_LIB_STAGING_DIR} /usr/lib)
  ENDIF(WINDOWS)
ENDMACRO(SET_TEST_PATH)