# @HEADER
# ************************************************************************
#
#            TriBITS: Tribal Build, Integrate, and Test System
#                    Copyright 2013 Sandia Corporation
#
# Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
# the U.S. Government retains certain rights in this software.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the Corporation nor the names of the
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# ************************************************************************
# @HEADER


INCLUDE(TribitsAddExecutableTestHelpers)
INCLUDE(TribitsGeneralMacros)
INCLUDE(TribitsTestCategories)

INCLUDE(CMakeParseArguments)
INCLUDE(GlobalSet)
INCLUDE(AppendGlobalSet)
INCLUDE(AppendStringVarWithSep)
INCLUDE(PrintVar)
INCLUDE(AdvancedSet)
INCLUDE(MessageWrapper)
INCLUDE(TribitsGetCategoriesString)


#
# Do initialization for test helpers
#
# This must be run just before the packages define their tests and this macro
# must be run in the base-level project scope.
#
MACRO(TRIBITS_ADD_TEST_HELPERS_INIT)
  IF (TPL_ENABLE_CUDA)
    SET(TRIBITS_TEST_EXTRA_ENVIRONMENT CTEST_KOKKOS_DEVICE_TYPE=gpus)
    SET(TRIBITS_RESOURCES_PER_PROCESS gpus:1)
  ENDIF()
ENDMACRO()


#
# Wrapper function for SET_TESTS_PROPERTIES() to be used in unit testing.
#

FUNCTION(TRIBITS_SET_TESTS_PROPERTIES)
  IF (NOT TRIBITS_ADD_TEST_ADD_TEST_UNITTEST)
    SET_TESTS_PROPERTIES(${ARGN})
  ENDIF()
  IF (TRIBITS_SET_TEST_PROPERTIES_CAPTURE_INPUT)
    APPEND_GLOBAL_SET(TRIBITS_SET_TEST_PROPERTIES_INPUT ${ARGN})
  ENDIF()
ENDFUNCTION()

#
# Wrapper function for SET_PROPERTY(TEST ...) to be used in unit testing
#

FUNCTION(TRIBITS_SET_TEST_PROPERTY)
  IF (NOT TRIBITS_ADD_TEST_ADD_TEST_UNITTEST)
    SET_PROPERTY(TEST ${ARGN})
  ENDIF()
  IF (TRIBITS_SET_TEST_PROPERTIES_CAPTURE_INPUT)
    APPEND_GLOBAL_SET(TRIBITS_SET_TEST_PROPERTIES_INPUT ${ARGN})
  ENDIF()
ENDFUNCTION()


#
# Scale a timeout by ${PROJECT_NAME}_SCALE_TEST_TIMEOUT
#
# This function will truncate input TIMEOUT_IN but will allow for a simple
# fractional scale factor.  It will also truncate the scale factor to just one
# decimal place.  CMake MATH(EXPR ...) does not support floating point so I
# have to use just manual integer arithmetic.
#
FUNCTION(TRIBITS_SCALE_TIMEOUT  TIMEOUT_IN  SCALED_TIMEOUT_OUT)

  IF (${PROJECT_NAME}_SCALE_TEST_TIMEOUT
    AND NOT "${${PROJECT_NAME}_SCALE_TEST_TIMEOUT}" EQUAL "1.0"
    )

    # Strip of any fractional part of the timeout
    SPLIT("${TIMEOUT_IN}" "[.]" TIMEOUT_IN_ARRAY)
    #PRINT_VAR(TIMEOUT_IN_ARRAY)
    LIST(GET TIMEOUT_IN_ARRAY 0 TIMEOUT_IN_TRUNCATED)
    #PRINT_VAR(TIMEOUT_IN_TRUNCATED)

    # Split the factional part of the scaling factor into SCALE_TEST_INT and
    # SCALE_TEST_INT_FRAC and do the math ourself with integer floating pint
    SPLIT("${${PROJECT_NAME}_SCALE_TEST_TIMEOUT}" "[.]" SCALE_TEST_ARRAY)
    LIST(LENGTH SCALE_TEST_ARRAY SCALE_TEST_ARRAY_LEN)
    #PRINT_VAR(SCALE_TEST_ARRAY_LEN)

    LIST(GET SCALE_TEST_ARRAY 0 SCALE_TEST_INT)
    #PRINT_VAR(SCALE_TEST_INT)

    MATH(EXPR TIMEOUT_USED
      "${TIMEOUT_IN_TRUNCATED} * ${SCALE_TEST_INT}")
    #PRINT_VAR(TIMEOUT_USED)

    IF ("${SCALE_TEST_ARRAY_LEN}" GREATER 1)
      # Handle the factional part (only take the first digit)
      LIST(GET SCALE_TEST_ARRAY 1 SCALE_TEST_INT_FRAC_FULL) # Could be more than 1 digit
      #PRINT_VAR(SCALE_TEST_INT_FRAC_FULL)
      STRING(SUBSTRING "${SCALE_TEST_INT_FRAC_FULL}" 0 1 SCALE_TEST_INT_FRAC)
      #PRINT_VAR(SCALE_TEST_INT_FRAC)
      MATH(EXPR TIMEOUT_USED
        "${TIMEOUT_USED} + (${SCALE_TEST_INT_FRAC} * ${TIMEOUT_IN_TRUNCATED}) / 10")
      #PRINT_VAR(TIMEOUT_USED)
    ENDIF()

  ELSE()

    SET(TIMEOUT_USED "${TIMEOUT_IN}")

  ENDIF()

  SET(${SCALED_TIMEOUT_OUT} ${TIMEOUT_USED} PARENT_SCOPE)

ENDFUNCTION()


#
# Function that converts a complete string of command-line arguments
# into a form that ADD_TEST(...) can correctly deal with.
#
# The main thing this function does is to replace spaces ' ' with
# array separators ';' since this is how ADD_TEST(...) expects to deal
# with command-line arguments, as array arguments.  However, this
# function will not do a replacement of ' ' with ';' if a quote is
# active.  This allows you to pass in quoted arguments and have them
# treated as a single argument.
#

FUNCTION(TRIBITS_CONVERT_CMND_ARG_STRING_TO_ADD_TEST_ARG_ARRAY CMND_ARG_STRING ARG_ARRAY_VARNAME)

  #MESSAGE("TRIBITS_CONVERT_CMND_ARG_STRING_TO_ADD_TEST_ARG_ARRAY")
  #PRINT_VAR(CMND_ARG_STRING)
  #PRINT_VAR(ARG_ARRAY_VARNAME)

  STRING(LENGTH ${CMND_ARG_STRING} STR_LEN)
  #PRINT_VAR(STR_LEN)

  MATH(EXPR STR_LAST_IDX "${STR_LEN}-1")

  SET(NEWSTR)

  SET(ACTIVE_QUOTE OFF)

  FOREACH(IDX RANGE ${STR_LAST_IDX})

    STRING(SUBSTRING ${CMND_ARG_STRING} ${IDX} 1 STR_CHAR)
    #PRINT_VAR(STR_CHAR)

    IF (STR_CHAR STREQUAL "\"")
      IF (NOT ACTIVE_QUOTE)
        SET(ACTIVE_QUOTE ON)
      ELSE()
        SET(ACTIVE_QUOTE OFF)
      ENDIF()
      #PRINT_VAR(ACTIVE_QUOTE)
    ENDIF()

    IF (NOT STR_CHAR STREQUAL " ")
      SET(NEWSTR "${NEWSTR}${STR_CHAR}")
    ELSE()
      IF (ACTIVE_QUOTE)
        SET(NEWSTR "${NEWSTR}${STR_CHAR}")
      ELSE()
        SET(NEWSTR "${NEWSTR};")
      ENDIF()
    ENDIF()

  ENDFOREACH()

  #PRINT_VAR(NEWSTR)

  SET(${ARG_ARRAY_VARNAME} ${NEWSTR} PARENT_SCOPE)

ENDFUNCTION()


#
# Determine if to add the test based on if testing is enabled for the current
# package or subpackage.
#

FUNCTION(TRIBITS_ADD_TEST_PROCESS_ENABLE_TESTS  ADD_THE_TEST_OUT)
  IF(${PACKAGE_NAME}_ENABLE_TESTS OR ${PARENT_PACKAGE_NAME}_ENABLE_TESTS)
   SET(ADD_THE_TEST TRUE)
  ELSE()
    IF (PARENT_PACKAGE_NAME STREQUAL PACKAGE_NAME)
      SET(PARENT_EANBLE_TESTS_DISABLE_MSG)
    ELSE()
      SET(PARENT_EANBLE_TESTS_DISABLE_MSG
	", ${PARENT_PACKAGE_NAME}_ENABLE_TESTS='${${PARENT_PACKAGE_NAME}_ENABLE_TESTS}'"
	)
    ENDIF()
    MESSAGE_WRAPPER(
      "-- ${TEST_NAME}: NOT added test because ${PACKAGE_NAME}_ENABLE_TESTS='${${PACKAGE_NAME}_ENABLE_TESTS}${PARENT_EANBLE_TESTS_DISABLE_MSG}'."
     )
   SET(ADD_THE_TEST FALSE)
  ENDIF()
  SET(${ADD_THE_TEST_OUT} ${ADD_THE_TEST} PARENT_SCOPE)
ENDFUNCTION()


#
# Determine if to add the test or not based on [X]HOST and [X]HOSTTYPE arguments
#
# Warning: Arguments for [X]HOST and [X]HOSTTYPE arguments are passed in
# implicitly due to scoping of CMake.
#

FUNCTION(TRIBITS_ADD_TEST_PROCESS_HOST_HOSTTYPE  ADD_THE_TEST_OUT)

  IF ("${${PROJECT_NAME}_HOSTNAME}" STREQUAL "")
    SET(${PROJECT_NAME}_HOSTNAME dummy_host)
  ENDIF()

  SET(ADD_THE_TEST TRUE)

  IF (ADD_THE_TEST)
    IF (NOT PARSE_HOST)
      SET (PARSE_HOST ${${PROJECT_NAME}_HOSTNAME})
    ENDIF()
    LIST (FIND PARSE_HOST ${${PROJECT_NAME}_HOSTNAME} INDEX_OF_HOSTNAME_IN_HOST_LIST)
    IF (${INDEX_OF_HOSTNAME_IN_HOST_LIST} EQUAL -1)
      SET(ADD_THE_TEST FALSE)
      SET(HOST_MATCH_MSG 
        "-- ${TEST_NAME}: NOT added test because ${PROJECT_NAME}_HOSTNAME='${${PROJECT_NAME}_HOSTNAME}' does not match list HOST='${PARSE_HOST}'!"
        )
    ENDIF()
  ENDIF()

  IF (ADD_THE_TEST)
    IF (NOT PARSE_XHOST)
      SET (PARSE_XHOST NONE)
    ENDIF()
    LIST (FIND PARSE_XHOST ${${PROJECT_NAME}_HOSTNAME} INDEX_OF_HOSTNAME_IN_XHOST_LIST)
    IF (NOT ${INDEX_OF_HOSTNAME_IN_XHOST_LIST} EQUAL -1)
      SET(ADD_THE_TEST FALSE)
      SET(HOST_MATCH_MSG 
        "-- ${TEST_NAME}: NOT added test because ${PROJECT_NAME}_HOSTNAME='${${PROJECT_NAME}_HOSTNAME}' matches list XHOST='${PARSE_XHOST}'!"
        )
    ENDIF()
  ENDIF()

  IF (ADD_THE_TEST)
    IF (NOT PARSE_HOSTTYPE)
      SET(PARSE_HOSTTYPE ${CMAKE_HOST_SYSTEM_NAME})
    ENDIF()
    LIST (FIND PARSE_HOSTTYPE ${CMAKE_HOST_SYSTEM_NAME} INDEX_OF_HOSTSYSTEMNAME_IN_HOSTTYPE_LIST)
    IF (${INDEX_OF_HOSTSYSTEMNAME_IN_HOSTTYPE_LIST} EQUAL -1)
      SET(ADD_THE_TEST FALSE)
      SET(HOST_MATCH_MSG 
        "-- ${TEST_NAME}: NOT added test because CMAKE_HOST_SYSTEM_NAME='${CMAKE_HOST_SYSTEM_NAME}' does not match list HOSTTYPE='${PARSE_HOSTTYPE}'!"
        )
    ENDIF()
  ENDIF()

  IF (ADD_THE_TEST)
    IF (NOT PARSE_XHOSTTYPE)
      SET(PARSE_XHOSTTYPE NONE)
    ENDIF()
    LIST (FIND PARSE_XHOSTTYPE ${CMAKE_HOST_SYSTEM_NAME} INDEX_OF_HOSTSYSTEMNAME_IN_XHOSTTYPE_LIST)
    IF (NOT ${INDEX_OF_HOSTSYSTEMNAME_IN_XHOSTTYPE_LIST} EQUAL -1)
      SET(ADD_THE_TEST FALSE)
      SET(HOST_MATCH_MSG 
        "-- ${TEST_NAME}: NOT added test because CMAKE_HOST_SYSTEM_NAME='${CMAKE_HOST_SYSTEM_NAME}' matches list XHOSTTYPE='${PARSE_XHOSTTYPE}'!"
        )
    ENDIF()
  ENDIF()

  FOREACH(VAR_NAME ${PARSE_EXCLUDE_IF_NOT_TRUE})
    IF (ADD_THE_TEST AND NOT ${VAR_NAME})
      SET(ADD_THE_TEST FALSE)
      SET(HOST_MATCH_MSG 
        "-- ${TEST_NAME}: NOT added test because EXCLUDE_IF_NOT_TRUE ${VAR_NAME}='${${VAR_NAME}}'!"
      )
    ENDIF()
  ENDFOREACH()

  IF (HOST_MATCH_MSG AND ${PROJECT_NAME}_TRACE_ADD_TEST)
    MESSAGE_WRAPPER("${HOST_MATCH_MSG}")
  ENDIF()

  SET(${ADD_THE_TEST_OUT} ${ADD_THE_TEST} PARENT_SCOPE)

ENDFUNCTION()


#
# Determine if to add the test or not based on CATEGORIES arguments
#
# Warning: Argument PARSE_CATEGORIES is passed in implicitly due to scoping of
# CMake.
#
FUNCTION(TRIBITS_ADD_TEST_PROCESS_CATEGORIES  ADD_THE_TEST_OUT)

  TRIBITS_FILTER_AND_ASSERT_CATEGORIES(PARSE_CATEGORIES)
  SET(PARSE_CATEGORIES ${PARSE_CATEGORIES} PARENT_SCOPE)

  SET(ADD_THE_TEST FALSE)

  # Set the default test-specific cateogry to basic if it is not set
  IF (NOT PARSE_CATEGORIES)
    SET (PARSE_CATEGORIES BASIC)
  ENDIF()

  #PRINT_VAR(${PROJECT_NAME}_TEST_CATEGORIES)
  #PRINT_VAR(PARSE_CATEGORIES)

  IF ("${${PROJECT_NAME}_TEST_CATEGORIES}" STREQUAL "")
    # In case this is not a TriBITS project!
    SET(${PROJECT_NAME}_TEST_CATEGORIES BASIC)
  ENDIF()

  # Process the test categories
  ASSERT_DEFINED(${PROJECT_NAME}_TEST_CATEGORIES)
  FOREACH(CATEGORY_USR_SET ${${PROJECT_NAME}_TEST_CATEGORIES})
    #PRINT_VAR(CATEGORY_USR_SET)
    #PRINT_VAR(PARSE_CATEGORIES)
    FOREACH(CATEGORY ${PARSE_CATEGORIES})
      IF (CATEGORY STREQUAL ${CATEGORY_USR_SET})
        # Exact match for the category, add the test
        SET(ADD_THE_TEST TRUE)
      ELSEIF(CATEGORY STREQUAL "BASIC")
        IF (CATEGORY_USR_SET STREQUAL "CONTINUOUS" OR CATEGORY_USR_SET STREQUAL "NIGHTLY"
          OR CATEGORY_USR_SET STREQUAL "HEAVY"
          )
          SET(ADD_THE_TEST TRUE)
        ENDIF()
      ELSEIF(CATEGORY STREQUAL "CONTINUOUS")
        IF (CATEGORY_USR_SET STREQUAL "NIGHTLY" OR CATEGORY_USR_SET STREQUAL "HEAVY")
          SET(ADD_THE_TEST TRUE)
        ENDIF()
      ELSEIF(CATEGORY STREQUAL "NIGHTLY")
        IF (CATEGORY_USR_SET STREQUAL "HEAVY")
          SET(ADD_THE_TEST TRUE)
        ENDIF()
      ELSE()
        # No matches for the category, don't add the test
      ENDIF()
    ENDFOREACH()
  ENDFOREACH()

  #PRINT_VAR(TEST_NAME)
  #PRINT_VAR(ADD_THE_TEST)
  #PRINT_VAR(${PROJECT_NAME}_TRACE_ADD_TEST)

  IF (TEST_NAME AND NOT ADD_THE_TEST AND ${PROJECT_NAME}_TRACE_ADD_TEST)
    MESSAGE_WRAPPER(
      "-- ${TEST_NAME}: NOT added test because ${PROJECT_NAME}_TEST_CATEGORIES='${${PROJECT_NAME}_TEST_CATEGORIES}' does not match this test's CATEGORIES='${PARSE_CATEGORIES}'!"
        )
  ENDIF()


  SET(${ADD_THE_TEST_OUT} ${ADD_THE_TEST} PARENT_SCOPE)
  #PRINT_VAR(${ADD_THE_TEST_OUT})

ENDFUNCTION()


#
# FUNCTION: TRIBITS_ADD_TEST_GET_EXE_BINARY_NAME()
#
# Get the full name of a package executable given its root name and other
# arguments.
#
# Usage:
#
#   TRIBITS_ADD_TEST_GET_EXE_BINARY_NAME(
#     <execRootName>
#     NOEXEPREFIX_IN
#     NOEXESUFFIX_IN
#     ADD_DIR_TO_NAME
#     EXE_BINARY_NAME_OUT
#     )
#
# By default, the full name of the executable is assumed to be::
#
#   ${PACKAGE_NAME}_<execName>${${PROJECT_NAME}_CMAKE_EXECUTABLE_SUFFIX}
#
FUNCTION(TRIBITS_ADD_TEST_GET_EXE_BINARY_NAME  EXE_NAME_IN
  NOEXEPREFIX_IN  NOEXESUFFIX_IN ADD_DIR_TO_NAME EXE_BINARY_NAME_OUT
  )
  SET(EXE_BINARY_NAME "${EXE_NAME_IN}")
  IF(PARSE_ADD_DIR_TO_NAME)
    SET(DIRECTORY_NAME "")
    TRIBITS_CREATE_NAME_FROM_CURRENT_SOURCE_DIRECTORY(DIRECTORY_NAME)
    SET(EXE_BINARY_NAME "${DIRECTORY_NAME}_${EXE_BINARY_NAME}")
  ENDIF()
  IF (NOEXESUFFIX_IN)
    SET(EXECUTABLE_SUFFIX "")
  ELSE()
    SET(EXECUTABLE_SUFFIX ${${PROJECT_NAME}_CMAKE_EXECUTABLE_SUFFIX})
  ENDIF()
  SET(EXE_BINARY_NAME "${EXE_BINARY_NAME}${EXECUTABLE_SUFFIX}")
  IF(PACKAGE_NAME AND NOT NOEXEPREFIX_IN)
    SET(EXE_BINARY_NAME ${PACKAGE_NAME}_${EXE_BINARY_NAME})
  ENDIF()
  SET(${EXE_BINARY_NAME_OUT} ${EXE_BINARY_NAME} PARENT_SCOPE)
ENDFUNCTION()


#
# Adjust the directory path to an executable for a test
#
FUNCTION(TRIBITS_ADD_TEST_ADJUST_DIRECTORY  EXE_BINARY_NAME  DIRECTORY
  EXECUTABLE_PATH_OUT
  )

   SET(EXECUTABLE_PATH "${EXE_BINARY_NAME}")

   IF (NOT IS_ABSOLUTE ${EXECUTABLE_PATH})

     IF (CMAKE_CONFIGURATION_TYPE)
       SET(EXECUTABLE_PATH "${CMAKE_CONFIGURATION_TYPE}/${EXECUTABLE_PATH}")
     ENDIF()

     IF (DIRECTORY)
       SET(EXECUTABLE_PATH "${DIRECTORY}/${EXECUTABLE_PATH}")
     ENDIF()

     IF (NOT IS_ABSOLUTE ${EXECUTABLE_PATH})
       SET(EXECUTABLE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE_PATH}")
     ENDIF()

   ENDIF()

  SET(${EXECUTABLE_PATH_OUT} ${EXECUTABLE_PATH} PARENT_SCOPE)

ENDFUNCTION()


#
# Get the number of MPI processes to use
#
FUNCTION(TRIBITS_ADD_TEST_GET_NUM_PROCS_USED  NUM_MPI_PROCS_IN
  NUM_MPI_PROCS_VAR_NAME_IN  NUM_MPI_PROCS_USED_OUT
  NUM_MPI_PROCS_USED_NAME_OUT
  )
  IF (NOT TPL_ENABLE_MPI)
    SET(MPI_EXEC_DEFAULT_NUMPROCS 1)
  ELSEIF (NOT DEFINED MPI_EXEC_DEFAULT_NUMPROCS)
    SET(MPI_EXEC_DEFAULT_NUMPROCS 1)
  ENDIF()
  IF (NOT TPL_ENABLE_MPI)
    SET(MPI_EXEC_MAX_NUMPROCS 1)
  ELSEIF (NOT DEFINED MPI_EXEC_MAX_NUMPROCS)
    SET(MPI_EXEC_MAX_NUMPROCS 1)
  ENDIF()
  IF (NOT NUM_MPI_PROCS_IN)
    SET(NUM_MPI_PROCS_IN ${MPI_EXEC_DEFAULT_NUMPROCS})
    SET(NUM_PROCS_VAR_NAME "MPI_EXEC_DEFAULT_NUMPROCS") 
  ELSE()
    SET(NUM_PROCS_VAR_NAME "${NUM_MPI_PROCS_VAR_NAME_IN}") 
  ENDIF()
  IF (${NUM_MPI_PROCS_IN} MATCHES [0-9]+-[0-9]+)
    STRING(REGEX REPLACE "([0-9]+)-([0-9]+)" "\\1" MIN_NP ${NUM_MPI_PROCS_IN} )
    STRING(REGEX REPLACE "([0-9]+)-([0-9]+)" "\\2" MAX_NP ${NUM_MPI_PROCS_IN} )
    IF(${MIN_NP} LESS ${MPI_EXEC_MAX_NUMPROCS} AND
      ${MAX_NP} GREATER ${MPI_EXEC_MAX_NUMPROCS}
      )
      SET(NUM_PROCS_USED ${MPI_EXEC_MAX_NUMPROCS})
    ELSEIF (${MIN_NP} EQUAL ${MPI_EXEC_MAX_NUMPROCS})
      SET(NUM_PROCS_USED ${MIN_NP})
    ELSEIF (${MAX_NP} EQUAL ${MPI_EXEC_MAX_NUMPROCS})
      SET(NUM_PROCS_USED ${MAX_NP})
    ELSEIF (${MAX_NP} LESS ${MPI_EXEC_MAX_NUMPROCS})
      SET(NUM_PROCS_USED ${MAX_NP})
    ELSE()
      # The number of available processors is outside the given range so the
      # test should not be run.
      SET(NUM_PROCS_USED -1)
    ENDIF()
  ELSEIF (${NUM_MPI_PROCS_IN} MATCHES [0-9]+,[0-9]+)
    MESSAGE(SEND_ERROR "The test ${TEST_NAME} can not be added yet"
      " because it we do not yet support the form of"
      " NUM_MPI_PROCS=${NUM_MPI_PROCS_IN}")
      SET(NUM_PROCS_USED -1)
  ELSE()
    IF(${NUM_MPI_PROCS_IN} GREATER ${MPI_EXEC_MAX_NUMPROCS})
      SET(NUM_PROCS_USED -1)
      IF (${PROJECT_NAME}_TRACE_ADD_TEST)
        MESSAGE_WRAPPER("-- ${TEST_NAME}: NOT added test because ${NUM_PROCS_VAR_NAME}='${NUM_MPI_PROCS_IN}' > MPI_EXEC_MAX_NUMPROCS='${MPI_EXEC_MAX_NUMPROCS}'!")
      ENDIF()
    ELSE()
      SET(NUM_PROCS_USED ${NUM_MPI_PROCS_IN})
    ENDIF()
  ENDIF()
  SET(${NUM_MPI_PROCS_USED_OUT}  ${NUM_PROCS_USED}  PARENT_SCOPE)
  SET(${NUM_MPI_PROCS_USED_NAME_OUT}  ${NUM_PROCS_VAR_NAME}  PARENT_SCOPE)
ENDFUNCTION()


#
# Generate the array of arguments for an MPI run
#
# NOTE: The extra test program arguments are passed through ${ARGN}.
#
FUNCTION( TRIBITS_ADD_TEST_GET_TEST_CMND_ARRAY  CMND_ARRAY_OUT
  EXECUTABLE_PATH  NUM_PROCS_USED
  )
  IF (TPL_ENABLE_MPI)
    SET(${CMND_ARRAY_OUT}
       "${MPI_EXEC}"
       ${MPI_EXEC_PRE_NUMPROCS_FLAGS}
       ${MPI_EXEC_NUMPROCS_FLAG} ${NUM_PROCS_USED}
       ${MPI_EXEC_POST_NUMPROCS_FLAGS}
       "${EXECUTABLE_PATH}"
       ${ARGN}
       PARENT_SCOPE
       )
  ELSE()
    SET(${CMND_ARRAY_OUT}
       "${EXECUTABLE_PATH}"
       ${ARGN}
       PARENT_SCOPE
       )
  ENDIF()
ENDFUNCTION()


#
# Get the number of cores used by process
#
FUNCTION(TRIBITS_ADD_TEST_GET_NUM_TOTAL_CORES_USED  TEST_NAME_IN
  NUM_TOTAL_CORES_USED_IN  NUM_TOTAL_CORES_USED_NAME_IN
  NUM_PROCS_USED_IN  NUM_PROCS_USED_NAME_IN
  NUM_TOTAL_CORES_USED_OUT  SKIP_TEST_OUT
  )

  SET(SKIP_TEST FALSE)

  IF (NUM_TOTAL_CORES_USED_IN)

    SET(NUM_TOTAL_CORES_USED  ${NUM_TOTAL_CORES_USED_IN})

    IF (NUM_TOTAL_CORES_USED_IN  GREATER  MPI_EXEC_MAX_NUMPROCS)
      IF (${PROJECT_NAME}_TRACE_ADD_TEST)
        MESSAGE_WRAPPER(
          "-- ${TEST_NAME_IN}: NOT added test because ${NUM_TOTAL_CORES_USED_NAME_IN}='${NUM_TOTAL_CORES_USED_IN}' > MPI_EXEC_MAX_NUMPROCS='${MPI_EXEC_MAX_NUMPROCS}'!"
          )
      ENDIF()
      SET(SKIP_TEST TRUE)
    ENDIF()

    IF (NUM_PROCS_USED_IN  GREATER  NUM_TOTAL_CORES_USED_IN)
      MESSAGE_WRAPPER(
        FATAL_ERROR
        "ERROR: ${TEST_NAME_IN}: ${NUM_PROCS_USED_NAME_IN}='${NUM_PROCS_USED}' > ${NUM_TOTAL_CORES_USED_NAME_IN}='${NUM_TOTAL_CORES_USED_IN}' not allowed!"
        )
      SET(SKIP_TEST TRUE) # For unit testing since above will not abort!
    ENDIF()

  ELSE()

    SET(NUM_TOTAL_CORES_USED  ${NUM_PROCS_USED_IN})

  ENDIF()

  SET(${NUM_TOTAL_CORES_USED_OUT}  ${NUM_TOTAL_CORES_USED}  PARENT_SCOPE)
  SET(${SKIP_TEST_OUT}  ${SKIP_TEST}  PARENT_SCOPE)

ENDFUNCTION()


#
# Read ${TEST_NAME_IN}_SET_DISABLED_AND_MSG and set output var
# <setDisabledAndMsgOut> as used by the TRIBITS_ADD[_ADVANCED]_TEST()
# functions.
#
# Usage:
#
#    TRIBITS_SET_DISABLED_AND_MSG(
#      <testName>              # The full test name passed to add_test()
#      "${PARSE_DISABLED}"     # From the input arg "DISABLED <msg>" to TA[A]T()
#      <setDisabledAndMsgOut>  # Sets var of this name on output
#      )
#
FUNCTION(TRIBITS_SET_DISABLED_AND_MSG  TEST_NAME_IN  PARSE_DISABLED
  SET_DISABLED_AND_MSG_OUT
  )

  SET(SET_DISABLED_AND_MSG "${PARSE_DISABLED}")
  IF (NOT "${${TEST_NAME_IN}_SET_DISABLED_AND_MSG}" STREQUAL "") # Override!
    SET(SET_DISABLED_AND_MSG "${${TEST_NAME_IN}_SET_DISABLED_AND_MSG}")
  ENDIF()

  SET(${SET_DISABLED_AND_MSG_OUT} "${SET_DISABLED_AND_MSG}" PARENT_SCOPE)

ENDFUNCTION()


#
# Read ${TEST_NAME_IN}_SET_RUN_SERIAL and set output var SET_RUN_SERIAL_OUT as
# used by the TRIBITS_ADD[_ADVANCED]_TEST() functions.
#
# Usage:
#
#    TRIBITS_SET_RUN_SERIAL(
#      <testName>              # The full test name passed to add_test()
#      "${PARSE_RUN_SERIAL}"   # From the input option "RUN_SERIAL" to TA[A]T()
#      <setRunSerialOut>       # Sets var of this name on output
#      )
#
FUNCTION(TRIBITS_SET_RUN_SERIAL  TEST_NAME_IN  PARSE_RUN_SERIAL
  SET_RUN_SERIAL_OUT
  )

  SET(SET_RUN_SERIAL "${PARSE_RUN_SERIAL}")
  IF (NOT "${${TEST_NAME_IN}_SET_RUN_SERIAL}" STREQUAL "") # Override!
    SET(SET_RUN_SERIAL "${${TEST_NAME_IN}_SET_RUN_SERIAL}")
  ENDIF()

  SET(${SET_RUN_SERIAL_OUT} "${SET_RUN_SERIAL}" PARENT_SCOPE)

ENDFUNCTION()


#
# Determine if the test should be skipped due to a disable var set
#
# Usage:
#
#   TRIBITS_ADD_TEST_QUERY_DISABLE(
#     <disableThisTestVarOut>  # Set var of this name on output
#     )
#
FUNCTION(TRIBITS_ADD_TEST_QUERY_DISABLE  DISABLE_TEST_VAR_OUT)

  #MESSAGE("TRIBITS_ADD_TEST_QUERY_DISABLE(): ${DISABLE_TEST_VAR_OUT} ${ARGN}")
  LIST(GET ARGN 0 TEST_NAME_IN)
  SET(TEST_NAME_DISABLE_VAR  ${TEST_NAME_IN}_DISABLE)
  #PRINT_VAR(${TEST_NAME_DISABLE_VAR})
  IF (${TEST_NAME_DISABLE_VAR})
    IF (${PROJECT_NAME}_TRACE_ADD_TEST)
      MESSAGE_WRAPPER(
        "-- ${TEST_NAME_IN}: NOT added test because ${TEST_NAME_DISABLE_VAR}='${${TEST_NAME_DISABLE_VAR}}'!")
    ENDIF()
    SET(${DISABLE_TEST_VAR_OUT} ON PARENT_SCOPE)
  ELSE()
    SET(${DISABLE_TEST_VAR_OUT} OFF PARENT_SCOPE)
  ENDIF()

ENDFUNCTION()


#
# Determine if to disable test due to <Package>_SKIP_CTEST_ADD_TEST=ON
#
FUNCTION(TRIBITS_ADD_TEST_PROCESS_SKIP_CTEST_ADD_TEST  ADD_THE_TEST_OUT)
  IF(${PACKAGE_NAME}_SKIP_CTEST_ADD_TEST OR ${PARENT_PACKAGE_NAME}_SKIP_CTEST_ADD_TEST)
    IF (PARENT_PACKAGE_NAME STREQUAL PACKAGE_NAME)
      SET(DISABLE_VAR_MSG
	"${PACKAGE_NAME}_SKIP_CTEST_ADD_TEST='${${PACKAGE_NAME}_SKIP_CTEST_ADD_TEST}'")
    ELSE()
      SET(DISABLE_VAR_MSG
	"${PARENT_PACKAGE_NAME}_SKIP_CTEST_ADD_TEST='${${PARENT_PACKAGE_NAME}_SKIP_CTEST_ADD_TEST}'")
    ENDIF()
    MESSAGE_WRAPPER(
      "-- ${TEST_NAME}: NOT added test because ${DISABLE_VAR_MSG}!")
   SET(ADD_THE_TEST FALSE)
  ELSE()
   SET(ADD_THE_TEST TRUE)
  ENDIF()
  SET(${ADD_THE_TEST_OUT} ${ADD_THE_TEST} PARENT_SCOPE)
ENDFUNCTION()


#
# Wrapper for adding a test to facilitate unit testing
#
FUNCTION(TRIBITS_ADD_TEST_ADD_TEST TEST_NAME EXE_NAME)

  IF (TRIBITS_ADD_TEST_ADD_TEST_CAPTURE)
    APPEND_GLOBAL_SET(TRIBITS_ADD_TEST_ADD_TEST_INPUT NAME ${TEST_NAME} COMMAND ${ARGN})
  ENDIF()

  IF (NOT TRIBITS_ADD_TEST_ADD_TEST_UNITTEST)
    ADD_TEST(NAME ${TEST_NAME} COMMAND ${ARGN})
  ENDIF()

  TRIBITS_SET_TESTS_PROPERTIES(${TEST_NAME} PROPERTIES REQUIRED_FILES ${EXE_NAME})

ENDFUNCTION()


#
# Set the pass/fail properties of a test that has already been added
#

FUNCTION(TRIBITS_PRIVATE_ADD_TEST_SET_PASSFAIL_PROPERTIES TEST_NAME_IN)

  # PASS_REGULAR_EXPRESSION

  IF (PARSE_STANDARD_PASS_OUTPUT)
    TRIBITS_SET_TESTS_PROPERTIES(${TEST_NAME_IN} PROPERTIES PASS_REGULAR_EXPRESSION
      "End Result: TEST PASSED")
  ENDIF()

  IF (PARSE_PASS_REGULAR_EXPRESSION)
    TRIBITS_SET_TESTS_PROPERTIES(${TEST_NAME_IN} PROPERTIES PASS_REGULAR_EXPRESSION
      ${PARSE_PASS_REGULAR_EXPRESSION})
  ENDIF()

  IF (PARSE_WILL_FAIL)
    TRIBITS_SET_TESTS_PROPERTIES(${TEST_NAME_IN} PROPERTIES WILL_FAIL ON)
  ENDIF()

  # FAIL_REGULAR_EXPRESSION

  IF (PARSE_FAIL_REGULAR_EXPRESSION)
    TRIBITS_SET_TEST_PROPERTY(${TEST_NAME_IN} APPEND PROPERTY FAIL_REGULAR_EXPRESSION
      "${PARSE_FAIL_REGULAR_EXPRESSION}")
  ENDIF()

  IF (${PACKAGE_NAME}_ENABLE_CIRCULAR_REF_DETECTION_FAILURE OR
    ${PROJECT_NAME}_ENABLE_CIRCULAR_REF_DETECTION_FAILURE
    )
    TRIBITS_SET_TEST_PROPERTY(${TEST_NAME_IN} APPEND PROPERTY FAIL_REGULAR_EXPRESSION
      "The following Teuchos::RCPNode objects were created")
    # NOTE: The above string must be kept in sync with the C++ code!
  ENDIF()
  # ToDo: Make a variable ${PROJECT_NAME}_EXTRA_FAIL_REGULAR_EXPRESSION and
  # move the above logic to Trilinos somehow.

ENDFUNCTION()


#
# Set the timeout for a test already aded
#
FUNCTION(TRIBITS_PRIVATE_ADD_TEST_SET_TIMEOUT  TEST_NAME_IN   TIMEOUT_USED_OUT)

  IF (PARSE_TIMEOUT)
    TRIBITS_SCALE_TIMEOUT("${PARSE_TIMEOUT}" TIMEOUT_USED)
    TRIBITS_SET_TESTS_PROPERTIES(${TEST_NAME_IN} PROPERTIES TIMEOUT ${TIMEOUT_USED})
  ELSE()
    SET(TIMEOUT_USED "")
  ENDIF()

  SET(${TIMEOUT_USED_OUT} ${TIMEOUT_USED} PARENT_SCOPE)

ENDFUNCTION()


#
# Set the environment for a test already aded
#
FUNCTION(TRIBITS_PRIVATE_ADD_TEST_SET_ENVIRONMENT  TEST_NAME_IN)

  IF (PARSE_ENVIRONMENT)
    TRIBITS_SET_TEST_PROPERTY(${TEST_NAME_IN} PROPERTY ENVIRONMENT ${PARSE_ENVIRONMENT})
  ENDIF()

ENDFUNCTION()


#
# Set the environment for a test already aded
#
FUNCTION(TRIBITS_PRIVATE_ADD_TEST_SET_PROCESSORS  TEST_NAME_IN
  NUM_TOTAL_CORES_USED_IN  PROCESSORS_OUT
  )

  SET(PROCESSORS_USED ${NUM_TOTAL_CORES_USED_IN})

  TRIBITS_SET_TESTS_PROPERTIES(${TEST_NAME_IN} PROPERTIES PROCESSORS
    "${PROCESSORS_USED}")

  SET(${PROCESSORS_OUT} ${PROCESSORS_USED} PARENT_SCOPE)

ENDFUNCTION()


#
# Print test added message!
#
FUNCTION(TRIBITS_PRIVATE_ADD_TEST_PRINT_ADDED  TEST_NAME_IN  CATEGORIES_IN
  NUM_MPI_PROCS_IN  PROCESSORS_IN  TIMEOUT_IN  RUN_SERIAL_IN  DISABLED_MSG_IN
  )

  SET(ADDED_TEST_PROPS "")
  TRIBITS_GET_CATEGORIES_STRING("${CATEGORIES_IN}" CATEGORIES_IN_COMMAS)
  APPEND_STRING_VAR_WITH_SEP(ADDED_TEST_PROPS
     ", "  "${CATEGORIES_IN_COMMAS}")
  IF (NUM_MPI_PROCS_IN AND TPL_ENABLE_MPI)
    APPEND_STRING_VAR_WITH_SEP(ADDED_TEST_PROPS
      ", "  "NUM_MPI_PROCS=${NUM_MPI_PROCS_IN}")
  ENDIF()
  IF (PROCESSORS_IN)
    APPEND_STRING_VAR_WITH_SEP(ADDED_TEST_PROPS
      ", "  "PROCESSORS=${PROCESSORS_IN}")
  ENDIF()
  IF (TIMEOUT_IN)
    APPEND_STRING_VAR_WITH_SEP(ADDED_TEST_PROPS
      ", "  "TIMEOUT=${TIMEOUT_IN}")
  ENDIF()

  IF (RUN_SERIAL_IN)
    APPEND_STRING_VAR_WITH_SEP(ADDED_TEST_PROPS
      ", "  "RUN_SERIAL")
  ENDIF()

  IF (DISABLED_MSG_IN)
    APPEND_STRING_VAR_WITH_SEP(ADDED_TEST_PROPS
      ", "  "DISABLED")
  ENDIF()

  IF (ADDED_TEST_PROPS)
   SET(ADDED_TEST_PROPS " (${ADDED_TEST_PROPS})") 
  ENDIF()

  IF (${PROJECT_NAME}_TRACE_ADD_TEST)
    MESSAGE_WRAPPER("-- ${TEST_NAME_IN}: Added test${ADDED_TEST_PROPS}!")
    IF (DISABLED_MSG_IN)
     MESSAGE_WRAPPER("--  => Reason DISABLED: ${DISABLED_MSG_IN}")
    ENDIF()
  ENDIF()

ENDFUNCTION()


#
# Overall add test command
#
# NOTE: Pass the command arguments on the end in ARGSN.
#
FUNCTION(TRIBITS_ADD_TEST_ADD_TEST_ALL  TEST_NAME_IN
  EXECUTABLE_PATH_IN  CATEGORIES_IN  NUM_PROCS_USED_IN  NUM_TOTAL_CORES_USED_IN
  RUN_SERIAL_IN  DISABLED_MSG_IN
  ADDED_TEST_NAME_OUT
  )

  TRIBITS_ADD_TEST_GET_TEST_CMND_ARRAY( CMND_ARRAY
    "${EXECUTABLE_PATH_IN}"  "${NUM_PROCS_USED_IN}" ${ARGN} )

  TRIBITS_ADD_TEST_QUERY_DISABLE(DISABLE_THIS_TEST  ${TEST_NAME_IN})

  IF (NOT  DISABLE_THIS_TEST)

    TRIBITS_ADD_TEST_ADD_TEST(${TEST_NAME_IN}  ${EXECUTABLE_PATH_IN}  ${CMND_ARRAY})
    SET(${ADDED_TEST_NAME_OUT}  ${TEST_NAME_IN}  PARENT_SCOPE)

    TRIBITS_PRIVATE_ADD_TEST_POST_PROCESS_ADDED_TEST(${TEST_NAME_IN}
      "${CATEGORIES_IN}" ${NUM_PROCS_USED_IN}  "${NUM_TOTAL_CORES_USED_IN}"
      "${RUN_SERIAL_IN}" "${DISABLED_MSG_IN}")

  ELSE()

    SET(${ADDED_TEST_NAME_OUT} "" PARENT_SCOPE)

  ENDIF()

ENDFUNCTION()


#
# Set the label and keywords
#

FUNCTION(TRIBITS_PRIVATE_ADD_TEST_ADD_LABEL_AND_KEYWORDS  TEST_NAME_IN)

  TRIBITS_SET_TEST_PROPERTY(${TEST_NAME_IN} APPEND PROPERTY
    LABELS ${PARENT_PACKAGE_NAME})

  IF(PARSE_KEYWORDS)
    TRIBITS_SET_TEST_PROPERTY(${TEST_NAME_IN} APPEND PROPERTY
      LABELS ${PARSE_KEYWORDS})
  ENDIF()

ENDFUNCTION()


#
# Postprocess a test that was added
#

FUNCTION(TRIBITS_PRIVATE_ADD_TEST_POST_PROCESS_ADDED_TEST  TEST_NAME_IN
  CATEGORIES_IN  NUM_PROCS_USED_IN  NUM_TOTAL_CORES_USED_IN
  RUN_SERIAL_IN  DISABLED_MSG_IN
  )

  TRIBITS_PRIVATE_ADD_TEST_SET_PASSFAIL_PROPERTIES(${TEST_NAME_IN})
  TRIBITS_PRIVATE_ADD_TEST_SET_ENVIRONMENT(${TEST_NAME_IN})
  TRIBITS_PRIVATE_ADD_TEST_SET_PROCESSORS(${TEST_NAME_IN}
    "${NUM_TOTAL_CORES_USED_IN}"  PROCESSORS_USED)
  TRIBITS_PRIVATE_ADD_TEST_SET_TIMEOUT(${TEST_NAME_IN}  TIMEOUT_USED)

  IF(RUN_SERIAL_IN)
    TRIBITS_SET_TESTS_PROPERTIES(${TEST_NAME_IN} PROPERTIES RUN_SERIAL ON)
  ENDIF()

  IF(DISABLED_MSG_IN)
    TRIBITS_SET_TESTS_PROPERTIES(${TEST_NAME_IN} PROPERTIES DISABLED ON)
  ENDIF()

  TRIBITS_PRIVATE_ADD_TEST_ADD_ENVIRONMENT_AND_RESOURCE(${TEST_NAME_IN}
     ${NUM_PROCS_USED_IN})

  TRIBITS_PRIVATE_ADD_TEST_ADD_LABEL_AND_KEYWORDS(${TEST_NAME_IN})

  TRIBITS_PRIVATE_ADD_TEST_PRINT_ADDED(${TEST_NAME_IN}
    "${CATEGORIES_IN}"  "${NUM_PROCS_USED_IN}"  "${PROCESSORS_USED}"
    "${TIMEOUT_USED}" "${RUN_SERIAL_IN}" "${DISABLED_MSG_IN}")

ENDFUNCTION()


#
# Add environment and resource properties to a test
#

FUNCTION(TRIBITS_PRIVATE_ADD_TEST_ADD_ENVIRONMENT_AND_RESOURCE  TEST_NAME_IN
  NUM_PROCS_USED_IN
  )
  IF(TRIBITS_TEST_EXTRA_ENVIRONMENT)
    TRIBITS_SET_TEST_PROPERTY(${TEST_NAME_IN} APPEND PROPERTY ENVIRONMENT
      "${TRIBITS_TEST_EXTRA_ENVIRONMENT}")
  ENDIF()

  IF(TRIBITS_RESOURCES_PER_PROCESS)
    SET(NUM_PROCESSES ${NUM_PROCS_USED_IN})
    IF(NOT NUM_PROCESSES OR NUM_PROCESSES LESS 1)
      SET(NUM_PROCESSES 1)
    ENDIF()
    TRIBITS_SET_TESTS_PROPERTIES(${TEST_NAME_IN} PROPERTIES RESOURCE_GROUPS
      "${NUM_PROCESSES},${TRIBITS_RESOURCES_PER_PROCESS}")
  ENDIF()
ENDFUNCTION()
