# @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(TribitsAddAdvancedTestHelpers)
include(TribitsConstants)

include(AppendStringVar)
include(PrintVar)


#
# @FUNCTION: tribits_add_advanced_test()
#
# Function that creates an advanced test defined by stringing together one or
# more executable and/or command invocations that is run as a ``cmake -P``
# script with very flexible pass/fail criteria.
#
# Usage::
#
#   tribits_add_advanced_test(
#     <testNameBase>
#     TEST_0 (EXEC <execTarget0> | CMND <cmndExec0>) ...
#     [TEST_1 (EXEC <execTarget1> | CMND <cmndExec1>) ...]
#     ...
#     [TEST_N (EXEC <execTargetN> | CMND <cmndExecN>) ...]
#     [OVERALL_WORKING_DIRECTORY (<overallWorkingDir> | TEST_NAME)]
#     [SKIP_CLEAN_OVERALL_WORKING_DIRECTORY]
#     [FAIL_FAST]
#     [RUN_SERIAL]
#     [KEYWORDS <keyword1> <keyword2> ...]
#     [COMM [serial] [mpi]]
#     [OVERALL_NUM_MPI_PROCS <overallNumProcs>]
#     [OVERALL_NUM_TOTAL_CORES_USED <overallNumTotalCoresUsed>]
#     [CATEGORIES <category0> <category1> ...]
#     [HOST <host0> <host1> ...]
#     [XHOST <host0> <host1> ...]
#     [HOSTTYPE <hosttype0> <hosttype1> ...]
#     [XHOSTTYPE <hosttype0> <hosttype1> ...]
#     [EXCLUDE_IF_NOT_TRUE <varname0> <varname1> ...]
#     [DISABLED <messageWhyDisabled>]
#     [FINAL_PASS_REGULAR_EXPRESSION <regex> |
#       FINAL_FAIL_REGULAR_EXPRESSION <regex>]
#     [ENVIRONMENT <var1>=<value1> <var2>=<value2> ...]
#     [TIMEOUT <maxSeconds>]
#     [ADDED_TEST_NAME_OUT <testName>]
#     )
#
# This function allows one to add a single CTest test that is actually a
# sequence of one or more separate commands strung together in some way to
# define the final pass/fail. One will want to use this function to add a test
# instead of `tribits_add_test()`_ when one needs to run more than one
# command, or one needs more sophisticated checking of the test result other
# than just grepping STDOUT (e.g. by running separate post-processing programs
# to examine output files).
#
# For more details on these arguments, see `TEST_<idx> EXEC/CMND Test Blocks
# and Arguments (tribits_add_advanced_test())`_.
#
# The most common type of an atomic test block ``TEST_<idx>`` runs a command
# as either a package-built executable or just any command.  An atomic test
# command block ``TEST_<idx>`` (i.e. ``TEST_0``, ``TEST_1``, ...) takes the
# form::
#
#   TEST_<idx>
#      (EXEC <exeRootName> [NOEXEPREFIX] [NOEXESUFFIX] [ADD_DIR_TO_NAME]
#             [DIRECTORY <dir>]
#         | CMND <cmndExec>)
#      [ARGS <arg1> <arg2> ... <argn>]
#      [MESSAGE "<message>"]
#      [WORKING_DIRECTORY <workingDir>]
#      [SKIP_CLEAN_WORKING_DIRECTORY]
#      [NUM_MPI_PROCS <numProcs>]
#      [NUM_TOTAL_CORES_USED <numTotalCoresUsed>]
#      [OUTPUT_FILE <outputFile>]
#      [NO_ECHO_OUTPUT]]
#      [PASS_ANY
#        | PASS_REGULAR_EXPRESSION "<regex>"
#        | PASS_REGULAR_EXPRESSION_ALL "<regex1>" "<regex2>" ... "<regexn>"
#        | STANDARD_PASS_OUTPUT ]
#      [FAIL_REGULAR_EXPRESSION "<regex>"]
#      [ALWAYS_FAIL_ON_NONZERO_RETURN | ALWAYS_FAIL_ON_ZERO_RETURN]
#      [WILL_FAIL]
#
# For more information on these arguments, see `TEST_<idx> EXEC/CMND Test
# Blocks and Arguments (tribits_add_advanced_test())`_.
#
# The other type of ``TEST_<idx>`` block supported is for copying files and
# takes the form::
#
#   TEST_<idx>
#     COPY_FILES_TO_TEST_DIR <file0> <file1> ... <filen>
#     [SOURCE_DIR <srcDir>]
#     [DEST_DIR <destDir>]
#
# This makes it easy to copy files from the source tree (or other location) to
# inside of the test directory (usually created with ``OVERALL_WORKING_DIR
# TEST_NAME``) so that tests can run in their own private working directory
# (and so these files get deleted and recopied each time the test runs).  This
# approach has several advantages:
#
# * One can modify the input files and then just run the test with ``ctest``
#   in an iterative manner (and not have to configure again when using
#   ``configure_file( ... COPYONLY)`` or build again when using
#   ``tribits_copy_files_to_binary_dir()`` in order to copy files).
#
# * When using ``OVERALL_WORKING_DIR TEST_NAME``, the test directory gets blow
#   away every time before it runs and therefore any old files are deleted
#   before the test gets run again (which avoids the problem of having a test
#   pass looking for the old files that will not be there when someone
#   configures and builds from scratch).
#
# For more information on these arguments, see `TEST_<idx>
# COPY_FILES_TO_TEST_DIR Test Blocks and Arguments
# (tribits_add_advanced_test())`_.
# 
# By default, each and every atomic ``TEST_<idx>`` block needs to pass (as
# defined in `Test case Pass/Fail (tribits_add_advanced_test())`_) in order
# for the overall test to pass.
#
# Finally, the test is only added if tests are enabled for the SE package
# (i.e. `${PACKAGE_NAME}_ENABLE_TESTS`_ ``= ON``) or the parent package (if
# this is a subpackage) (i.e. ``${PARENT_PACKAGE_NAME}_ENABLE_TESTS=ON``) or
# if other criteria are met (see some of the arguments in `Overall Arguments
# (tribits_add_advanced_test())`_ that can trigger a test to not be added).
# (NOTE: A more efficient way to optionally enable tests is to put them in a
# ``test/`` subdir and then include that subdir with
# `tribits_add_test_directories()`_.)
#
# *Sections:*
#
# * `Overall Arguments (tribits_add_advanced_test())`_
# * `TEST_<idx> EXEC/CMND Test Blocks and Arguments (tribits_add_advanced_test())`_
# * `TEST_<idx> COPY_FILES_TO_TEST_DIR Test Blocks and Arguments (tribits_add_advanced_test())`_
# * `Test case Pass/Fail (tribits_add_advanced_test())`_
# * `Overall Pass/Fail (tribits_add_advanced_test())`_
# * `Argument Parsing and Ordering (tribits_add_advanced_test())`_
# * `Implementation Details (tribits_add_advanced_test())`_
# * `Setting Additional Test Properties (tribits_add_advanced_test())`_
# * `Running multiple tests at the same time (tribits_add_advanced_test())`_
# * `Disabling Tests Externally (tribits_add_advanced_test())`_
# * `Debugging and Examining Test Generation (tribits_add_advanced_test())`_
# * `Using tribits_add_advanced_test() in non-TriBITS CMake projects`_
#
# .. _Overall Arguments (tribits_add_advanced_test()):
#
# **Overall Arguments (tribits_add_advanced_test())**
#
# Below, some of the overall arguments are described.  The rest of the overall
# arguments that control overall pass/fail are described in `Overall Pass/Fail
# (tribits_add_advanced_test())`_.  (NOTE: All of these arguments must be
# listed outside of the ``TEST_<idx>`` blocks, see `Argument Parsing and
# Ordering (tribits_add_advanced_test())`_).
#
#   ``<testNameBase>``
#
#     The base name of the test (which will have ``${PACKAGE_NAME}_``
#     prepended to the name, see <testName> below) that will be used to name
#     the output CMake script file as well as the CTest test name passed into
#     ``add_test()``.  This must be the first argument to this function.  The
#     name is allowed to contain '/' chars but these will be replaced with
#     '__' in the overall working directory name and the ctest -P script
#     (`Debugging and Examining Test Generation
#     (tribits_add_advanced_test())`_).
#
#   ``OVERALL_WORKING_DIRECTORY <overallWorkingDir>``
#
#     If specified, then the working directory ``<overallWorkingDir>``
#     (relative or absolute path) will be created and all of the test commands
#     by default will be run from within this directory.  If the value
#     ``<overallWorkingDir>=TEST_NAME`` is given, then the working directory
#     will be given the name ``<testName>`` where any '/' chars are replaced
#     with '__'.  By default, if the directory ``<overallWorkingDir>`` exists
#     before the test runs, it will be deleted and created again.  If one
#     wants to preserve the contents of this directory between test runs then
#     set ``SKIP_CLEAN_OVERALL_WORKING_DIRECTORY``.  Using a separate test
#     directory is a good option to use if the commands create intermediate
#     files and one wants to make sure they get deleted before the test cases
#     are run again.  It is also important to create a separate test directory
#     if multiple tests are defined in the same ``CMakeLists.txt`` file that
#     read/write files with the same name.
#
#   ``SKIP_CLEAN_OVERALL_WORKING_DIRECTORY``
#
#     If specified, then ``<overallWorkingDir>`` will **not** be deleted if it
#     already exists.
#
#   ``FAIL_FAST``
#
#     If specified, then the remaining test commands will be aborted when any
#     test command fails.  Otherwise, all of the test cases will be run.
#
#   ``RUN_SERIAL``
#
#     If specified, then no other tests will be allowed to run while this test
#     is running. See the ``RUN_SERIAL`` argument in the fucntion
#     `tribits_add_test()`_ for more details.
#
#   ``COMM [serial] [mpi]``
#
#     If specified, selects if the test will be added in serial and/or MPI
#     mode.  See the ``COMM`` argument in the function `tribits_add_test()`_
#     for more details.
#
#   ``OVERALL_NUM_MPI_PROCS <overallNumProcs>``
#
#     If specified, gives the default number of MPI processes that each
#     executable command runs on.  If ``<overallNumProcs>`` is greater than
#     ``${MPI_EXEC_MAX_NUMPROCS}`` then the test will be excluded.  If not
#     specified, then the default number of processes for an MPI build will be
#     ``${MPI_EXEC_DEFAULT_NUMPROCS}``.  For serial builds, this argument is
#     ignored.  For MPI builds with all ``TEST_<idx> CMND`` blocks,
#     ``<overallNumProcs>`` is used to set the property ``PROCESSORS``. (see
#     `Running multiple tests at the same time
#     (tribits_add_advanced_test())`_).  **WARNING!** If just running a serial
#     script or other command, then the property ``PROCESSORS`` will still get
#     set to ``${OVERALL_NUM_MPI_PROCS}`` so in order to avoid CTest
#     unnecessarily reserving ``${OVERALL_NUM_MPI_PROCS}`` processes for a
#     serial non-MPI test, then one must leave off ``OVERALL_NUM_MPI_PROCS``
#     or explicitly pass in ``MPI_EXEC_DEFAULT_NUMPROCS 1``!
#
#   ``OVERALL_NUM_TOTAL_CORES_USED <overallNumTotalCoresUsed>``
#
#     Used for ``NUM_TOTAL_CORES_USED`` if missing in a ``TEST_<idx>`` block.
#
#   ``CATEGORIES <category0> <category1> ...``
#
#     Gives the `Test Test Categories`_ for which this test will be added.
#     See `tribits_add_test()`_ for more details.
#
#   ``HOST <host0> <host1> ...``
#
#     The list of hosts for which to enable the test (see
#     `tribits_add_test()`_).
#
#   ``XHOST <host0> <host1> ...``
#
#     The list of hosts for which **not** to enable the test (see
#     `tribits_add_test()`_).
#
#   ``HOSTTYPE <hosttype0> <hosttype1> ...``
#
#     The list of host types for which to enable the test (see
#     `tribits_add_test()`_).
#
#   ``XHOSTTYPE <hosttype0> <hosttype1> ...``
#
#     The list of host types for which **not** to enable the test (see
#     `tribits_add_test()`_).
#
#   ``EXCLUDE_IF_NOT_TRUE <varname0> <varname1> ...``
#
#     If specified, gives the names of CMake variables that must evaluate to
#     true, or the test will not be added (see `tribits_add_test()`_).
#
#   ``DISABLED <messageWhyDisabled>``
#
#     If ``<messageWhyDisabled>`` is non-empty and does not evaluate to FALSE
#     by CMake, then the test will be added by ctest but the ``DISABLED`` test
#     property will be set (see `tribits_add_test()`_).
#
#   ``ENVIRONMENT <var1>=<value1> <var2>=<value2> ..``.
#
#     If passed in, the listed environment variables will be set before
#     calling the test.  This is set using the built-in CTest test property
#     ``ENVIRONMENT``.
#
#   ``TIMEOUT <maxSeconds>``
#
#     If passed in, gives maximum number of seconds the test will be allowed
#     to run before being timed-out and killed (see `Setting timeouts for
#     tests (tribits_add_test())`_).  This is for the full CTest test, not
#     individual ``TEST_<idx>`` commands!
#
#   ``ADDED_TEST_NAME_OUT <testName>``
#
#     If specified, then on output the variable ``<testName>`` will be set
#     with the name of the test passed to ``add_test()``.  Having this name
#     allows the calling ``CMakeLists.txt`` file access and set additional
#     test properties (see `Setting additional test properties
#     (tribits_add_advanced_test())`_).
#
# .. _TEST_<idx> EXEC/CMND Test Blocks and Arguments (tribits_add_advanced_test()):
#
#
# **TEST_<idx> EXEC/CMND Test Blocks and Arguments (tribits_add_advanced_test())**
#
# Each test general command block ``TEST_<idx>`` runs either a package-built
# test executable or some general command executable and is defined as either
# ``EXEC <exeRootName>`` or an arbitrary command ``CMND <cmndExec>`` with the
# arguments:
#
#   ``EXEC <exeRootName> [NOEXEPREFIX] [NOEXESUFFIX] [ADD_DIR_TO_NAME]
#   [DIRECTORY <dir>]``
#
#     If ``EXEC`` is specified, then ``<exeRootName>`` gives the root name of
#     an executable target that will be run as the command.  The full
#     executable name and path is determined in exactly the same way it is in
#     the `tribits_add_test()`_ function (see `Determining the Executable or
#     Command to Run (tribits_add_test())`_).  If this is an MPI build, then
#     the executable will be run with MPI using ``NUM_MPI_PROCS <numProcs>``
#     (or ``OVERALL_NUM_MPI_PROCS <overallNumProcs>`` if ``NUM_MPI_PROCS`` is
#     not set for this test case).  If the maximum number of MPI processes
#     allowed is less than this number of MPI processes, then the test will
#     *not* be run.  Note that ``EXEC <exeRootName>`` when ``NOEXEPREFIX`` and
#     ``NOEXESUFFIX`` are specified is basically equivalent to ``CMND
#     <cmndExec>`` except that in an MPI build, ``<exeRootName>`` is always
#     run using MPI.  In this case, one can pass in ``<exeRootName>`` to any
#     command one would like and it will get run with MPI in MPI mode just
#     link any other MPI-enabled built executable.
#
#   ``CMND <cmndExec>``
#
#     If ``CMND`` is specified, then ``<cmndExec>`` gives the executable for a
#     command to be run.  In this case, MPI will never be used to run the
#     executable even when configured in MPI mode
#     (i.e. ``TPL_ENABLE_MPI=ON``).  If one wants to run an arbitrary command
#     using MPI, use ``EXEC <fullPathToCmndExec> NOEXEPREFIX NOEXESUFFIX``
#     instead.  **WARNING:** If you want to run such tests using valgrind, you
#     have to use the raw executable as the ``<cmndExec>`` argument and *not*
#     the script.  For example, if you have a python script
#     ``my_python_test.py`` with ``/usr/bin/env pyhton`` at the top, you can't
#     just use::
#
#       CMND <path>/my_python_test.py ARGS <arg0> <arg1> ...
#
#     The same goes for Perl or any other scripting language.
#
#     Instead, you have to use::
#
#       CMND ${PYTHON_EXECUTABLE} ARGS <path>/my_python_test.py <arg0> <arg1> ...
#
# By default, the output (stdout/stderr) for each test command is captured and
# is then echoed to stdout for the overall test.  This is done in order to be
# able to grep the result to determine pass/fail.
#
# Other miscellaneous arguments for each ``TEST_<idx>`` block include:
#
#   ``DIRECTORY <dir>``
#
#     If specified, then the executable is assumed to be in the directory
#     given by relative ``<dir>``.  See `tribits_add_test()`_.
#
#   ``MESSAGE "<message>"``
#
#     If specified, then the string in ``"<message>"`` will be printed before
#     this test command is run.  This allows adding some documentation about
#     each individual test invocation to make the test output more
#     understandable.
#
#   ``WORKING_DIRECTORY <workingDir>``
#
#     If specified, then the working directory ``<workingDir>`` (relative or
#     absolute) will be created and the test will be run from within this
#     directory.  If the directory ``<workingDir>`` exists before the test
#     runs, it will be deleted and created again.  If one wants to preserve
#     the contents of this directory between test blocks, then one needs to
#     set ``SKIP_CLEAN_WORKING_DIRECTORY``.  Using a different
#     ``WORKING_DIRECTORY`` for individual test commands allows creating
#     independent working directories for each test case.  This would be
#     useful if a single ``OVERALL_WORKING_DIRECTORY`` was not sufficient for
#     some reason.
#
#   ``SKIP_CLEAN_WORKING_DIRECTORY``
#
#     If specified, then ``<workingDir>`` will **not** be deleted if it
#     already exists.
#
#   ``NUM_MPI_PROCS <numProcs>``
#
#     If specified, then ``<numProcs>`` is the number of processors used for
#     MPI executables.  If not specified, this will default to
#     ``<overallNumProcs>`` from ``OVERALL_NUM_MPI_PROCS <overallNumProcs>``.
#     If that is not specified, then the value is taken from
#     ``${MPI_EXEC_DEFAULT_NUMPROCS}``.  For serial builds
#     (i.e. ``TPL_ENABLE_MPI=OFF``), passing in a value ``<numMpiProcs>`` >
#     ``1`` will cause the entire test to not be added.
#
#   ``NUM_TOTAL_CORES_USED <numTotalCoresUsed>``
#
#     If specified, gives the total number of processes used by this
#     command/executable.  If this is missing, but ``NUM_MPI_PROCS
#     <numProcs>`` is specified, then ``<numProcs>`` is used instead.  If
#     ``NUM_TOTAL_CORES_USED`` is missing BUT ``OVERALL_NUM_TOTAL_CORES_USED
#     <overallNumTotalCoresUsed>`` is, then ``<overallNumTotalCoresUsed>`` is
#     used for ``<numTotalCoresUsed>``.  This argument is used for test
#     scripts/executables that use more cores than MPI processes
#     (i.e. ``<numProcs>``) and its only purpose is to inform CTest and
#     TriBITS of the maximum number of cores that are used by the underlying
#     test executable/script.  When ``<numTotalCoresUsed>`` is greater than
#     ``${MPI_EXEC_MAX_NUMPROCS}``, then the test will not be added.
#     Otherwise, the CTest property ``PROCESSORS`` is set to the max over all
#     ``<numTotalCoresUsed>`` so that CTest knows how to best schedule the
#     test w.r.t. other tests on a given number of available processes.
#
#   ``OUTPUT_FILE <outputFile>``
#
#     If specified, then stdout and stderr for the test case will be sent to
#     ``<outputFile>``.  By default, the contents of this file will **also**
#     be printed to STDOUT unless ``NO_ECHO_OUT`` is passed as well.
#
#     NOTE: Contrary to CMake documentation for execute_process(), STDOUT and
#     STDERR may not get output in the correct order interleaved correctly,
#     even in serial without MPI.  Therefore, you can't write any tests that
#     depend on the order of STDOUT and STDERR output in relation to each
#     other.  Also note that all of STDOUT and STDERR will be first read into
#     the CTest executable process main memory before the file
#     ``<outputFile>`` is written.  Therefore, don't run executables or
#     commands that generate massive amounts of console output or it may
#     exhaust main memory.  Instead, have the command or executable write
#     directly to a file instead of going through STDOUT.
#
#   ``NO_ECHO_OUTPUT``
#
#     If specified, then the output for the test command will not be echoed to
#     the output for the entire test command.
#
# By default, an individual test case ``TEST_<IDX>`` is assumed to pass if the
# executable or commands returns a non-zero value to the shell.  However, a
# test case can also be defined to pass or fail based on the arguments/options
# (see `Test case Pass/Fail (tribits_add_advanced_test())`_):
#
#   ``PASS_ANY``
#
#     If specified, the test command will be assumed to pass regardless of
#     the return value or any other output.  This would be used when a command
#     that is to follow will determine pass or fail based on output from this
#     command in some way.
#
#   ``PASS_REGULAR_EXPRESSION "<regex>"``
#
#     If specified, the test command will be assumed to pass if it matches the
#     given regular expression.  Otherwise, it is assumed to fail.  TIPS:
#     Replace ';' with '[;]' or CMake will interpret this as an array elemnt
#     boundary.  To match '.', use '[.]'.
#
#   ``PASS_REGULAR_EXPRESSION_ALL "<regex1>" "<regex2>" ... "<regexn>"``
#
#     If specified, the test command will be assumed to pass if the output
#     matches all of the provided regular expressions.  Note that this is not
#     a capability of raw ctest and represents an extension provided by
#     TriBITS.  NOTE: It is critical that you replace ';' with '[;]' or CMake
#     will interpret this as an array elemnt boundary.
#
#   ``STANDARD_PASS_OUTPUT``
#
#     If specified, the test command will be assumed to pass if the string
#     expression "Final Result: PASSED" is found in the output for the test.
#     This as the result of directly passing in ``PASS_REGULAR_EXPRESSION
#     "End Result: TEST PASSED"``.
#
#   ``FAIL_REGULAR_EXPRESSION "<regex>"``
#
#     If specified, the test command will be assumed to fail if it matches the
#     given regular expression.  Otherwise, it is assumed to pass.  This will
#     be applied and take precedence over other above pass criteria.  For
#     example, if even if ``PASS_REGULAR_EXPRESSION`` or
#     ``PASS_REGULAR_EXPRESSION_ALL`` match, then the test will be marked as
#     failed if this fail regex matches the output.
#
#   ``ALWAYS_FAIL_ON_NONZERO_RETURN``
#
#     If specified, then the test case will be marked as failed if the test
#     command returns nonzero, independent of the other pass/fail criteria.
#     This option is used in cases where one wants to grep for strings in the
#     output but still wants to require a zero return code.  This make for a
#     stronger test by requiring that both the strings are found and that the
#     command returns 0.
#
#   ``ALWAYS_FAIL_ON_ZERO_RETURN``
#
#     If specified, then the test case will be marked as failed if the test
#     command returns zero '0', independent of the other pass/fail criteria.
#     This option is used in cases where one wants to grep for strings in the
#     output but still wants to require a nonzero return code.  This make for
#     a stronger test by requiring that both the strings are found and that
#     the command returns != 0.
#
#   ``WILL_FAIL``
#
#      If specified, invert the result from the other pass/fail criteria.  For
#      example, if the regexes in ``PASS_REGULAR_EXPRESSION`` or
#      ``PASS_REGULAR_EXPRESSION_ALL`` indicate that a test should pass, then
#      setting ``WILL_FAIL`` will invert that and report the test as failing.
#      But typically this is used to report a test that returns a nonzero code
#      as passing.
# 
# All of the arguments for a test block ``TEST_<idx>`` must appear directly
# below their ``TEST_<idx>`` argument and before the next test block (see
# `Argument Parsing and Ordering (tribits_add_advanced_test())`_).
#
# **NOTE:** The current implementation limits the number of ``TEST_<idx>``
# blocks to just 20 (i.e. for ``<idx>=0...19``).  If more test blocks are
# added (e.g. ``TEST_20``), then an fatal error message will be printed and
# processing will end.  To increase this max in a local scope, call::
#
#   set(TRIBITS_ADD_ADVANCED_TEST_MAX_NUM_TEST_BLOCKS <larger-num>)
#
# where ``<larger-num> > 20``.  This can be set in any scope in any
# ``CMakeLists.txt`` file or inside of a function and it will impact all of
# the future calls to ``tribits_add_advanced_test()`` in that scope.
#
# .. _TEST_<idx> COPY_FILES_TO_TEST_DIR Test Blocks and Arguments (tribits_add_advanced_test()):
#
# **TEST_<idx> COPY_FILES_TO_TEST_DIR Test Blocks and Arguments (tribits_add_advanced_test())**
#
# The arguments for the ``TEST_<idx>`` ``COPY_FILES_TO_TEST_DIR`` block are:
#
#   ``COPY_FILES_TO_TEST_DIR <file0> <file1> ... <filen>``
#
#     Required list of 1 or more file names for files that will be copied from
#     ``<srcDir>/`` to ``<destDir>/``.
#
#   ``SOURCE_DIR <srcDir>``
#
#     Optional source directory where the files will be copied from.  If
#     ``<srcDir>`` is not given, then it is assumed to be
#     ``${CMAKE_CURRENT_SOURCE_DIR}``.  If ``<srcDir>`` is given but is a
#     relative path, then it is interpreted relative to
#     ``${CMAKE_CURRENT_SOURCE_DIR}``.  If ``<srcDir>`` is an absolute path,
#     then that path is used without modification.
#
#   ``DEST_DIR <destDir>``
#
#     Optional destination directory where the files will be copied to.  If
#     ``<destDir>`` is not given, then it is assumed to be the working
#     directory where the test is running (typically a new directory created
#     under ``${CMAKE_CURRENT_BINARY_DIR}`` when ``OVERALL_WORKING_DIR
#     TEST_NAME`` is given).  If ``<destDir>`` is given but is a relative
#     path, then it is interpreted relative to the current test working
#     directory.  If ``<destDir>`` is an absolute path, then that path is used
#     without modification.  If ``<destDir>`` does not exist, then it will be
#     created (including several directory levels deep if needed).
#
# .. _Test case Pass/Fail (tribits_add_advanced_test()):
#
# **Test case Pass/Fail (tribits_add_advanced_test())**
#
# The logic for how pass/fail for a ``TEST_<IDX>`` ``EXEC`` or ``CMND`` case
# is applied is given by::
#
#   # A) Apply first set of pass/fail logic
#   TEST_CASE_PASSED = FALSE
#   If PASS_ANY specified:
#     TEST_CASE_PASSED = TRUE
#   Else If PASS_REGULAR_EXPRESSION specified and "<regex>" matches:
#     TEST_CASE_PASSED = TRUE
#   Else if PASS_REGULAR_EXPRESSION_ALL specified:
#     TEST_CASE_PASSED = TRUE
#     For each "<regexi>":
#       If "<regixi>" does not match:
#         TEST_CASE_PASSED = FALSE
#   Else
#     If command return code == 0:
#       TEST_CASE_PASSED = TRUE
#     Endif
#   Endif
#
#   # B) Check for failing regex matching?
#   If FAIL_REGULAR_EXPRESSION specified and "<regex>" matches:
#     TEST_CASE_PASSED = FALSE
#   Endif
#
#   # C) Check for return code always 0 or !=0?
#   If ALWAYS_FAIL_ON_NONZERO_RETURN specified and return code != 0:
#     TEST_CASE_PASSED = FALSE
#   ElseIf ALWAYS_FAIL_ON_ZERO_RETURN specified and return code == 0:
#     TEST_CASE_PASSED = FALSE
#   Endif
#
#   # D) Invert pass/fail result?
#   If WILL_FAIL specified:
#     If TEST_CASE_PASSED:
#       TEST_CASE_PASSED = FALSE
#     Else
#       TEST_CASE_PASSED = TRUE
#     Endif
#   Endif
#
# .. _Overall Pass/Fail (tribits_add_advanced_test()):
#
# **Overall Pass/Fail (tribits_add_advanced_test())**
#
# By default, the overall test will be assumed to pass if it prints::
#
#   "OVERALL FINAL RESULT: TEST PASSED (<testName>)"
#
# However, this can be changed by setting one of the following optional arguments:
#
#   ``FINAL_PASS_REGULAR_EXPRESSION <regex>``
#
#     If specified, the test will be assumed to pass if the output matches
#     ``<regex>``.  Otherwise, it will be assumed to fail.
#
#   ``FINAL_FAIL_REGULAR_EXPRESSION <regex>``
#
#     If specified, the test will be assumed to fail if the output matches
#     ``<regex>``.  Otherwise, it will be assumed to fail.
#
# .. _Argument Parsing and Ordering (tribits_add_advanced_test()):
#
# **Argument Parsing and Ordering (tribits_add_advanced_test())**
#
# The basic tool used for parsing the arguments to this function is the macro
# ``cmake_parse_arguments()`` which has a certain set of behaviors.  The
# parsing using ``cmake_parse_arguments()`` is actually done in two phases.
# There is a top-level parsing of the "overall" arguments listed in `Overall
# Arguments (tribits_add_advanced_test())`_ that also pulls out the test
# blocks.  Then there is a second level of parsing using
# ``cmake_parse_arguments()`` for each of the ``TEST_<idx>`` blocks.  Because
# of this usage, there are a few restrictions that one needs to be aware of
# when using ``tribits_add_advanced_test()``.  This short sections tries to
# explain the behaviors and what is allowed and what is not allowed.
#
# For the most part, the "overall" arguments and the arguments inside of any
# individual ``TEST_<idx>`` blocks can be listed in any order but there are
# restrictions related to the grouping of overall arguments and ``TEST_<idx>``
# blocks which are as follows:
#
# * The ``<testNameBase>`` argument must be the first listed (it is the only
#   positional argument).
#
# * The test cases ``TEST_<idx>`` must be listed in order (i.e. ``TEST_0
#   ... TEST_1 ...``) and the test cases must be consecutive integers
#   (e.g. can't jump from ``TEST_5`` to ``TEST_7``).
#
# * All of the arguments for a test case must appear directly below its
#   ``TEST_<idx>`` keyword and before the next ``TEST_<idx+1>`` keyword or
#   before any trailing overall keyword arguments.
#
# * None of the overall arguments (e.g. ``CATEGORIES``) can be listed inside
#   of a ``TEST_<idx>`` block but otherwise can be listed before or after all
#   of the ``TEST_<idx>`` blocks.  (NOTE: The current implementation will
#   actually allow overall arguments to be listed after all of the local
#   arguments before the next TEST_<idx> block but this is confusing and will
#   not be allowed in a future implementation).
#
# Other than that, the keyword arguments and options can appear in any order.
#
# .. ToDo: Add some examples of bad argument ordering and what will happen.
#
# .. _Implementation Details (tribits_add_advanced_test()):
#
# **Implementation Details (tribits_add_advanced_test())**
#
# Since raw CTest does not support the features provided by this function, the
# way an advanced test is implemented is that a ``cmake -P`` script with the
# name ``<testName>.cmake`` (with any '/' replaced with '__') gets created in
# the current binary directory that then gets added to CTest using::
#
#   add_test(<testName> cmake [other options] -P <testName>.cmake)
#
# This ``cmake -P`` script then runs the various test cases and checks the
# pass/fail for each case to determine overall pass/fail and implement other
# functionality described above.
#
# .. _Setting Additional Test Properties (tribits_add_advanced_test()):
#
# **Setting Additional Test Properties (tribits_add_advanced_test())**
#
# After this function returns, if the test gets added using ``add_test()``,
# then additional properties can be set and changed using
# ``set_tests_properties(<testName> ...)``, where ``<testName>`` is returned
# using the ``ADDED_TEST_NAME_OUT <testName>`` argument.  Therefore, any tests
# properties that are not directly supported by this function and passed
# through the argument list to this wrapper function can be set in the outer
# ``CMakeLists.txt`` file after the call to ``tribits_add_advanced_test()``.
# For example::
#
#   tribits_add_advanced_test_test( someTest ...
#     ADDED_TEST_NAME_OUT  someTest_TEST_NAME )
#
#   if (someTest_TEST_NAME)
#     set_tests_properties( ${someTest_TEST_NAME}
#       PROPERTIES ATTACHED_FILES someTest.log )
#   endif()
#
# where the test writes a log file ``someTest.log`` that we want to submit to
# CDash also.
#
# This approach will work no matter what TriBITS names the individual test(s)
# or whether the test(s) are added or not (depending on other arguments like
# ``COMM``, ``XHOST``, etc.).
#
# The following built-in CTest test properties are set through `Overall
# Arguments (tribits_add_advanced_test())`_ or are otherwise automatically set
# by this function and should **NOT** be overridden by direct calls to
# ``set_tests_properties()``: ``ENVIRONMENT``, ``FAIL_REGULAR_EXPRESSION``,
# ``LABELS``, ``PASS_REGULAR_EXPRESSION``, ``RUN_SERIAL``, ``TIMEOUT``,
# ``WILL_FAIL``, and ``WORKING_DIRECTORY``.
#
# However, generally, other built-in CTest test properties can be set after
# the test is added like show above.  Examples of test properties that can be
# set using direct calls to ``set_tests_properties()`` include
# ``ATTACHED_FILES``, ``ATTACHED_FILES_ON_FAIL``, ``COST``, ``DEPENDS``,
# ``MEASUREMENT``, and ``RESOURCE_LOCK``.
#
# For example, one can set a dependency between two tests using::
#
#   tribits_add_advanced_test_test( test_a [...]
#      ADDED_TEST_NAME_OUT  test_a_TEST_NAME )
#   
#   tribits_add_advanced_test_test( test_b [...]
#      ADDED_TEST_NAME_OUT  test_z_TEST_NAME )
#   
#   if (test_a_TEST_NAME AND test_b_TEST_NAME)
#     set_tests_properties(${test_b_TEST_NAME}
#       PROPERTIES DEPENDS ${test_a_TEST_NAME})
#   endif()
#
# This ensures that test ``test_b`` will always be run after ``test_a`` if
# both tests are run by CTest.
#
# .. _Running multiple tests at the same time (tribits_add_advanced_test()):
#
# **Running multiple tests at the same time (tribits_add_advanced_test())**
#
# Just as with `tribits_add_test()`_, setting ``NUM_MPI_PROCS <numProcs>`` or
# ``OVERALL_NUM_MPI_PROCS <numOverallProcs>`` or ``NUM_TOTAL_CORES_USED
# <numTotalCoresUsed>`` or ``OVERALL_NUM_TOTAL_CORES_USED
# <overallNumTotalCoresUsed>`` will set the ``PROCESSORS`` CTest property to
# allow CTest to schedule and run multiple tests at the same time when ``'ctest
# -j<N>'`` is used (see `Running multiple tests at the same time
# (tribits_add_test())`_).
#
# .. _Disabling Tests Externally (tribits_add_advanced_test()):
#
# **Disabling Tests Externally (tribits_add_advanced_test())**
#
# The test can be disabled externally by setting the CMake cache variable
# ``<testName>_DISABLE=TRUE``.  This allows tests to be disabled on a
# case-by-case basis.  The name ``<testName>`` must be the *exact* name
# that shows up in ``ctest -N`` when running the test.
#
# .. _Debugging and Examining Test Generation (tribits_add_advanced_test()):
#
# **Debugging and Examining Test Generation (tribits_add_advanced_test())**
#
# In order to see what tests get added and if not then why, configure with
# ``${PROJECT_NAME}_TRACE_ADD_TEST=ON``.  That will print one line per test
# that shows that the test got added or not and if not then why the test was
# not added (i.e. due to ``COMM``, ``OVERALL_NUM_MPI_PROCS``,
# ``NUM_MPI_PROCS``, ``CATEGORIES``, ``HOST``, ``XHOST``, ``HOSTTYPE``, or
# ``XHOSTTYPE``).
#
# Likely the best way to debug test generation using this function is to
# examine the generated file ``<testName>.cmake`` in the current binary
# directory (see `Implementation Details (tribits_add_advanced_test())`_) and
# the generated ``CTestTestfile.cmake`` file that should list this test case.
#
# .. _Using tribits_add_advanced_test() in non-TriBITS CMake projects:
#
# **Using tribits_add_advanced_test() in non-TriBITS CMake projects**
#
# The function ``tribits_add_advanced_test()`` can be used to add tests in
# non-TriBITS projects.  To do so, one just needs to set the variables
# ``PROJECT_NAME``, ``PACKAGE_NAME`` (which could be the same as
# ``PROJECT_NAME``), ``${PACKAGE_NAME}_ENABLE_TESTS=TRUE``, and
# ``${PROJECT_NAME}_TRIBITS_DIR`` (pointing to the TriBITS location).  For example,
# a valid project can be a simple as::
#
#   cmake_minimum_required(VERSION 3.17.0)
#   set(PROJECT_NAME TAATDriver)
#   project(${PROJECT_NAME} NONE)
#   set(${PROJECT_NAME}_TRACE_ADD_TEST TRUE)
#   set(${PROJECT_NAME}_TRIBITS_DIR ""  CACHE FILEPATH
#     "Location of TriBITS to use." ) 
#   set(PACKAGE_NAME ${PROJECT_NAME})
#   set(${PACKAGE_NAME}_ENABLE_TESTS TRUE)
#   set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
#     ${TRIBITS_DIR}/core/utils
#     ${TRIBITS_DIR}/core/package_arch )
#   include(TribitsAddAdvancedTest)
#   include(CTest)
#   enable_testing()
#   
#   tribits_add_advanced_test(
#     TAAT_COPY_FILES_TO_TEST_DIR_bad_file_name
#     OVERALL_WORKING_DIRECTORY TEST_NAME
#     TEST_0 CMND echo ARGS "Hello World!"
#       PASS_REGULAR_EXPRESIOIN "Hello World"
#     )
#
function(tribits_add_advanced_test TEST_NAME_IN)

  if (${PROJECT_NAME}_VERBOSE_CONFIGURE)
    message("\nPACKAGE_ADD_ADVANCED_TEST: ${TEST_NAME_IN}\n")
  endif()

  global_set(TRIBITS_SET_TEST_PROPERTIES_INPUT)
  global_set(MESSAGE_WRAPPER_INPUT)

  # Set the full TEST_NAME
  if (PACKAGE_NAME)
    set(TEST_NAME ${PACKAGE_NAME}_${TEST_NAME_IN})
  else()
    set(TEST_NAME ${TEST_NAME_IN})
  endif()

  # Avoid quoted strings lookup variables
  cmake_policy(SET CMP0054 NEW)
  # NOTE: For some reason, setting this policy at the top level with TriBITS
  # in TribitsCMakePolices.cmake does not affect this function.  Therefore, I
  # have to set it again here.

  #
  # A) Parse the overall arguments and figure out how many tests
  # commands we will have
  #

  # Set maximum number of TEST_<idx> blocks
  tribits_add_advanced_test_max_num_test_cmnd_idx_compute()
  set(MAX_NUM_TEST_CMND_IDX ${TRIBITS_ADD_ADVANCED_TEST_MAX_NUM_TEST_CMND_IDX})

  set(TEST_IDX_LIST "")
  foreach( TEST_CMND_IDX RANGE ${MAX_NUM_TEST_CMND_IDX})
    list( APPEND TEST_IDX_LIST TEST_${TEST_CMND_IDX} )
  endforeach()
  #print_var(TEST_IDX_LIST)

  cmake_parse_arguments(
     #prefix
     PARSE
     #options
     "FAIL_FAST;RUN_SERIAL;SKIP_CLEAN_OVERALL_WORKING_DIRECTORY"
     # one_value_keywords
     "DISABLED"
     # multi_value_keywords
     "${TEST_IDX_LIST};OVERALL_WORKING_DIRECTORY;KEYWORDS;COMM;OVERALL_NUM_MPI_PROCS;OVERALL_NUM_TOTAL_CORES_USED;FINAL_PASS_REGULAR_EXPRESSION;CATEGORIES;HOST;XHOST;HOSTTYPE;XHOSTTYPE;EXCLUDE_IF_NOT_TRUE;FINAL_FAIL_REGULAR_EXPRESSION;TIMEOUT;ENVIRONMENT;ADDED_TEST_NAME_OUT"
     ${ARGN}
     )

  tribits_add_advanced_test_check_exceed_max_num_test_blocks()

  if(PARSE_ADDED_TEST_NAME_OUT)
    set(${PARSE_ADDED_TEST_NAME_OUT} "" PARENT_SCOPE )
  endif()

  # Set the name of the cmake -P script.
  string(REPLACE "/" "__" NORMALIZED_TEST_NAME "${TEST_NAME}")
  set(TEST_SCRIPT_FILE_NAME "${NORMALIZED_TEST_NAME}.cmake")

  # Set the relative overall working directory and abs working directory
  if (PARSE_OVERALL_WORKING_DIRECTORY)
    if ("${PARSE_OVERALL_WORKING_DIRECTORY}" STREQUAL "TEST_NAME")
      set(PARSE_OVERALL_WORKING_DIRECTORY ${NORMALIZED_TEST_NAME})
    endif()
   # Test will run in created working subdir
   set(ABS_OVERALL_WORKING_DIRECTORY
     ${CMAKE_CURRENT_BINARY_DIR}/${PARSE_OVERALL_WORKING_DIRECTORY})
  else()
    # Test runs in current binary directory (not a good idea!) 
    set(ABS_OVERALL_WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
  endif()

  #
  # B) Add or don't add tests based on a number of criteria
  #

  set(ADD_THE_TEST FALSE)
  tribits_add_test_process_enable_tests(ADD_THE_TEST)
  if (NOT ADD_THE_TEST)
    return()
  endif()

  tribits_add_test_process_skip_ctest_add_test(ADD_THE_TEST)
  if (NOT ADD_THE_TEST)
    return()
  endif()

  set(ADD_THE_TEST FALSE)
  tribits_add_test_process_categories(ADD_THE_TEST)
  if (NOT ADD_THE_TEST)
    return()
  endif()

  set(ADD_THE_TEST FALSE)
  tribits_add_test_process_host_hosttype(ADD_THE_TEST)
  if (NOT ADD_THE_TEST)
    return()
  endif()

  tribits_add_test_query_disable(DISABLE_THIS_TEST ${TEST_NAME})
  if (DISABLE_THIS_TEST)
    return()
  endif()

  tribits_set_run_serial(${TEST_NAME} "${PARSE_RUN_SERIAL}"
    SET_RUN_SERIAL)

  tribits_set_disabled_and_msg(${TEST_NAME} "${PARSE_DISABLED}"
    SET_DISABLED_AND_MSG)  # Adds the test but sets DISABLED test prop!

  #
  # C) Determine if we will add the serial or MPI tests based on input COMM
  # and TPL_ENABLE_MPI
  #

  tribits_process_comm_args(ADD_SERIAL_TEST  ADD_MPI_TEST  ${PARSE_COMM})
  if (NOT ADD_SERIAL_TEST AND NOT ADD_MPI_TEST)
    return()
  endif()

  #
  # D) Build the test script
  #

  set(ADD_THE_TEST TRUE)

  set(TEST_SCRIPT_STR "")

  append_string_var( TEST_SCRIPT_STR
    "\n"
    "#\n"
    "# This is a CMake script and must be run as \"cmake -P <SCRIPT_NAME>\"\n"
    "#\n"
    "# NOTE: To see what commands this script runs, run it as:\n"
    "#\n"
    "#    $ cmake -DSHOW_COMMANDS_ONLY=ON -P <SCRIPT_NAME>\n"
    "#\n"
    "\n"
    "#\n"
    "# Variables\n"
    "#\n"
    "\n"
    "set( TEST_NAME ${TEST_NAME} )\n"
    )

  # Loop through each test case

  set(NUM_CMNDS 0)
  set(TEST_EXE_LIST "")

  if (PARSE_OVERALL_NUM_MPI_PROCS  AND  PARSE_OVERALL_NUM_TOTAL_CORES_USED)
    if (PARSE_OVERALL_NUM_MPI_PROCS  GREATER  PARSE_OVERALL_NUM_TOTAL_CORES_USED)
      message_wrapper(FATAL_ERROR
        "ERROR: ${TEST_NAME}: OVERALL_NUM_MPI_PROCS='${PARSE_OVERALL_NUM_MPI_PROCS}' > OVERALL_NUM_TOTAL_CORES_USED='${PARSE_OVERALL_NUM_TOTAL_CORES_USED}' not allowed!")
      return()
    endif()
  endif()

  # ToDo: Assert that OVERALL_NUM_TOTAL_CORES_USED >= OVERALL_NUM_MPI_PROCS

  if (PARSE_OVERALL_NUM_MPI_PROCS)
    set(MAX_NUM_MPI_PROCS_USED ${PARSE_OVERALL_NUM_MPI_PROCS})
    set(MAX_NUM_PROCESSORS_USED ${PARSE_OVERALL_NUM_MPI_PROCS})
  else()
    set(MAX_NUM_MPI_PROCS_USED ${PARSE_OVERALL_NUM_MPI_PROCS})
    set(MAX_NUM_PROCESSORS_USED 1)
  endif()

  set(HAS_AT_LEAST_ONE_EXEC FALSE)

  foreach( TEST_CMND_IDX RANGE ${MAX_NUM_TEST_CMND_IDX} )

    if (NOT PARSE_TEST_${TEST_CMND_IDX} )
      break()
    endif()

    if (NOT  ADD_THE_TEST)
      break()
    endif()

    math( EXPR NUM_CMNDS ${NUM_CMNDS}+1 )

    # Parse the test command case

    #print_var(PARSE_TEST_${TEST_CMND_IDX})

    # Search to see if we are copying files or not for this TEST_<IDX> block ...

    set(PARSE_COPY_FILES_TO_TEST_DIR)
    set(COPY_FILES_TO_TEST_DIR_IDX FALSE)
    foreach(PARSE_TEST_IDX_ARGS ${PARSE_TEST_${TEST_CMND_IDX}})
      if (PARSE_TEST_IDX_ARGS STREQUAL "COPY_FILES_TO_TEST_DIR")
        set(COPY_FILES_TO_TEST_DIR_IDX TRUE)
      endif()
    endforeach()

    if (COPY_FILES_TO_TEST_DIR_IDX)

      # Do a special parse just for TEST_<IDX> blocks of type
      # COPY_FILES_TO_TEST_DIR

      cmake_parse_arguments(
         #prefix
         PARSE
         #options
         ""
         # one_value_keywords
         ""
         # multi_value_keywords
         "COPY_FILES_TO_TEST_DIR;SOURCE_DIR;DEST_DIR" 
	 # Arguments to parse
         ${PARSE_TEST_${TEST_CMND_IDX}}
         )
      tribits_check_for_unparsed_arguments()
      tribits_assert_parse_arg_one_or_more_values(PARSE COPY_FILES_TO_TEST_DIR)
      tribits_assert_parse_arg_zero_or_one_value(PARSE SOURCE_DIR)
      tribits_assert_parse_arg_zero_or_one_value(PARSE DEST_DIR)

      set(PARSE_EXEC)
      set(PARSE_CMND)

    else()

      # Parse TEST_<IDX> block args for types EXEC and CMND

      cmake_parse_arguments(
         #prefix
         PARSE
         #options
          "NOEXEPREFIX;NOEXESUFFIX;NO_ECHO_OUTPUT;PASS_ANY;STANDARD_PASS_OUTPUT;ALWAYS_FAIL_ON_NONZERO_RETURN;ALWAYS_FAIL_ON_ZERO_RETURN;WILL_FAIL;ADD_DIR_TO_NAME;SKIP_CLEAN_WORKING_DIRECTORY"
         # one_value_keywords
         ""
         # multi_value_keywords
         "EXEC;CMND;ARGS;DIRECTORY;MESSAGE;WORKING_DIRECTORY;OUTPUT_FILE;NUM_MPI_PROCS;NUM_TOTAL_CORES_USED;PASS_REGULAR_EXPRESSION_ALL;FAIL_REGULAR_EXPRESSION;PASS_REGULAR_EXPRESSION"
         ${PARSE_TEST_${TEST_CMND_IDX}}
         )
  
      tribits_check_for_unparsed_arguments()

    endif()

    #
    # Set up the command that will be written into the cmake -P *.cmake file
    #

    set(ARGS_STR ${PARSE_ARGS})
    #print_var(ARGS_STR)
    #if (PARSE_ARGS)
    #  tribits_join_exec_process_set_args( ARGS_STR ${PARSE_ARGS} )
    #endif()

    if (PARSE_EXEC)

      #
      # This is an EXEC test block
      #

      set(HAS_AT_LEAST_ONE_EXEC TRUE)

      list( LENGTH PARSE_EXEC PARSE_EXEC_LEN )
      if (NOT PARSE_EXEC_LEN EQUAL 1)
        message(SEND_ERROR "Error, TEST_${TEST_CMND_IDX} EXEC = '${PARSE_EXEC}'"
          " must be a single name.  To add arguments use ARGS <arg1> <arg2> ...." )
      endif()

      tribits_add_test_get_exe_binary_name( "${PARSE_EXEC}"
        ${PARSE_NOEXEPREFIX} ${PARSE_NOEXESUFFIX}
        ${PARSE_ADD_DIR_TO_NAME} EXE_BINARY_NAME )

      tribits_add_test_adjust_directory( ${EXE_BINARY_NAME} "${PARSE_DIRECTORY}"
        EXECUTABLE_PATH)

      if (PARSE_NUM_MPI_PROCS)
        set(NUM_MPI_PROC_VAR_NAME "NUM_MPI_PROCS")
      else()
        set(PARSE_NUM_MPI_PROCS ${PARSE_OVERALL_NUM_MPI_PROCS})
        set(NUM_MPI_PROC_VAR_NAME "OVERALL_NUM_MPI_PROCS")
      endif()

      tribits_add_test_get_num_procs_used("${PARSE_NUM_MPI_PROCS}"
        "${NUM_MPI_PROC_VAR_NAME}"  NUM_PROCS_USED  NUM_PROCS_USED_NAME)

      if (NUM_PROCS_USED LESS 0)
        set(ADD_THE_TEST FALSE)
      elseif (NUM_PROCS_USED  GREATER  MAX_NUM_MPI_PROCS_USED)
        set(MAX_NUM_MPI_PROCS_USED  ${NUM_PROCS_USED})
      endif()

      if (PARSE_NUM_TOTAL_CORES_USED)
        set(NUM_TOTAL_CORES_USED  ${PARSE_NUM_TOTAL_CORES_USED})
        set(NUM_TOTAL_CORES_USED_NAME  "NUM_TOTAL_CORES_USED")
      else()
        set(NUM_TOTAL_CORES_USED  ${PARSE_OVERALL_NUM_TOTAL_CORES_USED})
        set(NUM_TOTAL_CORES_USED_NAME  "OVERALL_NUM_TOTAL_CORES_USED")
      endif()

      tribits_add_test_get_num_total_cores_used("${TEST_NAME}${MPI_NAME_POSTFIX}"
        "${NUM_TOTAL_CORES_USED}"  "${NUM_TOTAL_CORES_USED_NAME}"
        "${NUM_PROCS_USED}"  "${NUM_PROCS_USED_NAME}"
        NUM_TOTAL_CORES_USED  SKIP_TEST)
      if (SKIP_TEST)
        set(ADD_THE_TEST FALSE)
      endif()

      if (NUM_TOTAL_CORES_USED  GREATER  MAX_NUM_PROCESSORS_USED)
        set(MAX_NUM_PROCESSORS_USED  ${NUM_TOTAL_CORES_USED})
      endif()

      if(ADD_THE_TEST)
        list(APPEND TEST_EXE_LIST ${EXECUTABLE_PATH})
      endif()

      tribits_add_test_get_test_cmnd_array( TEST_CMND_ARRAY
        "${EXECUTABLE_PATH}" "${NUM_PROCS_USED}" ${ARGS_STR} )
      #print_var(TEST_CMND_ARRAY)

    elseif (PARSE_CMND)

      #
      # This is a COMMAND test block
      #

      list( LENGTH PARSE_CMND PARSE_CMND_LEN )
      if (NOT PARSE_CMND_LEN EQUAL 1)
        message_wrapper(SEND_ERROR "Error, TEST_${TEST_CMND_IDX} CMND = '${PARSE_CMND}'"
          " must be a single command.  To add arguments use ARGS <arg1> <arg2> ...." )
      endif()

      if (PARSE_NUM_TOTAL_CORES_USED)
        set(NUM_TOTAL_CORES_USED  ${PARSE_NUM_TOTAL_CORES_USED})
        set(NUM_TOTAL_CORES_USED_NAME  "NUM_TOTAL_CORES_USED")
      else()
        set(NUM_TOTAL_CORES_USED  ${PARSE_OVERALL_NUM_TOTAL_CORES_USED})
        set(NUM_TOTAL_CORES_USED_NAME  "OVERALL_NUM_TOTAL_CORES_USED")
      endif()

      tribits_add_test_get_num_total_cores_used("${TEST_NAME}${MPI_NAME_POSTFIX}"
        "${NUM_TOTAL_CORES_USED}"  "${NUM_TOTAL_CORES_USED_NAME}"
        "1"  "DUMMY_NUM_MPI_PROCS"  # Never be printed
        NUM_TOTAL_CORES_USED  SKIP_TEST)
      if (SKIP_TEST)
        set(ADD_THE_TEST FALSE)
      endif()

      if (NUM_TOTAL_CORES_USED  GREATER  MAX_NUM_PROCESSORS_USED)
        set(MAX_NUM_PROCESSORS_USED  ${NUM_TOTAL_CORES_USED})
      endif()

      if(ADD_THE_TEST)
        if (NOT TRIBITS_ADD_TEST_ADD_TEST_UNITTEST)
          find_program(CMND_PATH ${PARSE_CMND})
        else()
          set(CMND_PATH ${PARSE_CMND})
        endif()
        list(APPEND TEST_EXE_LIST ${CMND_PATH})
      endif()

      set( TEST_CMND_ARRAY ${PARSE_CMND} ${ARGS_STR} )

    elseif (PARSE_COPY_FILES_TO_TEST_DIR)

      #
      # This is a COPY_FLES_TO_TEST_DIR block
      #

      # FILES_TO_COPY_COMMA_SEP
      set(FILES_TO_COPY_COMMA_SEP "${PARSE_COPY_FILES_TO_TEST_DIR}")
      string(REPLACE ";" "," FILES_TO_COPY_COMMA_SEP
        "${FILES_TO_COPY_COMMA_SEP}" )
      # NOTE: Above, we have to replace ';' with ',' or the lower commands
      # append_string_var() will replace ';' with ''.  This is *not* what we
      # want.  In DriveAdvancedTest.cmake, we will replace the ',' with ';'
      # again :-)  

      # SOURCE_DIR
      if (PARSE_SOURCE_DIR)
        if (IS_ABSOLUTE "${PARSE_SOURCE_DIR}")
          set(COPY_FILES_TO_TEST_DIR_SOURCE_DIR
            "${PARSE_SOURCE_DIR}")
        else()
          set(COPY_FILES_TO_TEST_DIR_SOURCE_DIR
            "${CMAKE_CURRENT_SOURCE_DIR}/${PARSE_SOURCE_DIR}")
        endif()
      else()
        set(COPY_FILES_TO_TEST_DIR_SOURCE_DIR
          "${CMAKE_CURRENT_SOURCE_DIR}")
      endif()

      # DEST_DIR
      if (PARSE_DEST_DIR)
        if (IS_ABSOLUTE "${PARSE_DEST_DIR}")
          set(COPY_FILES_TO_TEST_DIR_DEST_DIR
            "${PARSE_DEST_DIR}")
        else()
          set(COPY_FILES_TO_TEST_DIR_DEST_DIR
            "${ABS_OVERALL_WORKING_DIRECTORY}/${PARSE_DEST_DIR}")
        endif()
      else()
        set(COPY_FILES_TO_TEST_DIR_DEST_DIR
          "${ABS_OVERALL_WORKING_DIRECTORY}")
      endif()

    else()

      message( FATAL_ERROR
        "Must have EXEC, CMND, or COPY_FILES_TO_TEST_DIR for TEST_${TEST_CMND_IDX}" )

    endif()

    #
    # Write parts for this TEST_<IDX> block to TEST_SCRIPT_STR
    #

    if (PARSE_COPY_FILES_TO_TEST_DIR)

      # Write the vars for COPY_FILES_TO_TEST_DIR 
  
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_COPY_FILES_TO_TEST_DIR"
        " \"${FILES_TO_COPY_COMMA_SEP}\")\n"
        )
      if (TRIBITS_ADD_ADVANCED_TEST_UNITTEST)
        global_set(TRIBITS_ADD_ADVANCED_TEST_CMND_ARRAY_${TEST_CMND_IDX}
          "${TEST_CMND_STR}" )
      endif()
  
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_SOURCE_DIR"
        " \"${COPY_FILES_TO_TEST_DIR_SOURCE_DIR}\")\n"
        )
  
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_DEST_DIR"
        " \"${COPY_FILES_TO_TEST_DIR_DEST_DIR}\")\n"
        )

    else()

      # Write the command to be run for EXEC and CMND blocks ...

      tribits_join_exec_process_set_args( TEST_CMND_STR "${TEST_CMND_ARRAY}" )
      #print_var(TEST_CMND_STR)
  
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_CMND ${TEST_CMND_STR} )\n"
        )
      if (TRIBITS_ADD_ADVANCED_TEST_UNITTEST)
        global_set(TRIBITS_ADD_ADVANCED_TEST_CMND_ARRAY_${TEST_CMND_IDX}
          "${TEST_CMND_STR}" )
      endif()

    endif()

    if (PARSE_MESSAGE)
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_MESSAGE \"${PARSE_MESSAGE}\" )\n"
        )
    endif()

    if (PARSE_WORKING_DIRECTORY)
      if ("${PARSE_WORKING_DIRECTORY}" STREQUAL "TEST_NAME")
        set(PARSE_WORKING_DIRECTORY ${TEST_NAME})
      endif()
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_WORKING_DIRECTORY \"${PARSE_WORKING_DIRECTORY}\" )\n"
         )
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_SKIP_CLEAN_WORKING_DIRECTORY ${PARSE_SKIP_CLEAN_WORKING_DIRECTORY} )\n"
        )
    endif()

    if (PARSE_OUTPUT_FILE)
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_OUTPUT_FILE \"${PARSE_OUTPUT_FILE}\" )\n"
        )
    endif()

    if (PARSE_NO_ECHO_OUTPUT)
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_NO_ECHO_OUTPUT \"${PARSE_NO_ECHO_OUTPUT}\" )\n"
        )
    endif()

    # Set up pass/fail

    if (PARSE_PASS_ANY)
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_PASS_ANY TRUE )\n"
        )
    elseif (PARSE_STANDARD_PASS_OUTPUT)
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_PASS_REGULAR_EXPRESSION \"End Result: TEST PASSED\" )\n"
        )
    elseif (PARSE_PASS_REGULAR_EXPRESSION)
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_PASS_REGULAR_EXPRESSION \"${PARSE_PASS_REGULAR_EXPRESSION}\" )\n"
        )
    elseif (PARSE_PASS_REGULAR_EXPRESSION_ALL)
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_PASS_REGULAR_EXPRESSION_ALL "
        )
      foreach(REGEX_STR ${PARSE_PASS_REGULAR_EXPRESSION_ALL})
        append_string_var( TEST_SCRIPT_STR
          "\"${REGEX_STR}\" "
          )
      endforeach()
      append_string_var( TEST_SCRIPT_STR
        ")\n"
        )
    endif()

    if (PARSE_FAIL_REGULAR_EXPRESSION)
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_FAIL_REGULAR_EXPRESSION \"${PARSE_FAIL_REGULAR_EXPRESSION}\" )\n"
        )
    endif()

    if (PARSE_ALWAYS_FAIL_ON_NONZERO_RETURN)
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_ALWAYS_FAIL_ON_NONZERO_RETURN TRUE )\n"
        )
    endif()

    if (PARSE_ALWAYS_FAIL_ON_ZERO_RETURN)
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_ALWAYS_FAIL_ON_ZERO_RETURN TRUE )\n"
        )
    endif()

    if (PARSE_WILL_FAIL)
      append_string_var( TEST_SCRIPT_STR
        "\n"
        "set( TEST_${TEST_CMND_IDX}_WILL_FAIL TRUE )\n"
        )
    endif()

  endforeach()

  # ToDo: Verify that TEST_${MAX_NUM_TEST_CMND_IDX}+1 does *not* exist!

  #
  # F) Set the CTest test to run the new script
  #

  if (ADD_THE_TEST)

    #
    # F.1) Call add_test() and set the test properties
    #
  
    set(TEST_SCRIPT_FILE "${CMAKE_CURRENT_BINARY_DIR}/${TEST_SCRIPT_FILE_NAME}")

    if(NOT TRIBITS_ADD_TEST_ADD_TEST_UNITTEST)
      # Tell CTest to run our script for this test.  Pass the test-type
      # configuration name to the script in the TEST_CONFIG variable.
      add_test( NAME ${TEST_NAME}
        COMMAND ${CMAKE_COMMAND} "-DTEST_CONFIG=\${CTEST_CONFIGURATION_TYPE}"
        -P "${TEST_SCRIPT_FILE}")
    endif()

    if(PARSE_ADDED_TEST_NAME_OUT)
      set(${PARSE_ADDED_TEST_NAME_OUT} ${TEST_NAME} PARENT_SCOPE )
    endif()

    list(REMOVE_DUPLICATES TEST_EXE_LIST)
    tribits_set_test_property(${TEST_NAME} PROPERTY REQUIRED_FILES ${TEST_EXE_LIST})

    if(SET_RUN_SERIAL)
      tribits_set_tests_properties(${TEST_NAME} PROPERTIES RUN_SERIAL ON)
    endif()

    tribits_private_add_test_add_label_and_keywords(${TEST_NAME})

    #This if clause will set the number of PROCESSORS to reserve during testing
    #to the number requested for the test.
    if(MAX_NUM_PROCESSORS_USED)
      tribits_set_tests_properties(${TEST_NAME} PROPERTIES
        PROCESSORS "${MAX_NUM_PROCESSORS_USED}")
    endif()

    tribits_private_add_test_add_environment_and_resource(${TEST_NAME}
      ${MAX_NUM_PROCESSORS_USED})

    if (SET_DISABLED_AND_MSG)
      tribits_set_tests_properties(${TEST_NAME} PROPERTIES DISABLED ON)
    endif()

    if (PARSE_FINAL_PASS_REGULAR_EXPRESSION)
      tribits_set_tests_properties( ${TEST_NAME} PROPERTIES
        PASS_REGULAR_EXPRESSION "${PARSE_FINAL_PASS_REGULAR_EXPRESSION}" )
    elseif (PARSE_FINAL_FAIL_REGULAR_EXPRESSION)
      tribits_set_tests_properties( ${TEST_NAME} PROPERTIES
        FAIL_REGULAR_EXPRESSION "${PARSE_FINAL_FAIL_REGULAR_EXPRESSION}" )
    else()
      tribits_set_tests_properties( ${TEST_NAME} PROPERTIES
        PASS_REGULAR_EXPRESSION
        "OVERALL FINAL RESULT: TEST PASSED .${TEST_NAME}." )
    endif()

    tribits_private_add_test_set_timeout(${TEST_NAME}  TIMEOUT_USED)

    tribits_private_add_test_set_environment(${TEST_NAME})

    if (TPL_ENABLE_MPI AND HAS_AT_LEAST_ONE_EXEC)
      set(MAX_NUM_MPI_PROCS_USED_TO_PRINT  ${MAX_NUM_MPI_PROCS_USED})
    else()
      set(MAX_NUM_MPI_PROCS_USED_TO_PRINT "")
    endif()

    tribits_private_add_test_print_added(${TEST_NAME}
      "${PARSE_CATEGORIES}"  "${MAX_NUM_MPI_PROCS_USED_TO_PRINT}"
      "${MAX_NUM_PROCESSORS_USED}"  "${TIMEOUT_USED}"
      "${SET_RUN_SERIAL}" "${SET_DISABLED_AND_MSG}")

    #
    # F.2) Write the cmake -P script
    #
  
    append_string_var( TEST_SCRIPT_STR
      "\n"
      "set(PROJECT_NAME ${PROJECT_NAME})\n"
      "\n"
      "set(${PROJECT_NAME}_TRIBITS_DIR ${${PROJECT_NAME}_TRIBITS_DIR})\n"
      "\n"
      "set(TEST_NAME ${TEST_NAME})\n"
      "\n"
      "set(NUM_CMNDS ${NUM_CMNDS})\n"
      "\n"
      "set(OVERALL_WORKING_DIRECTORY \"${PARSE_OVERALL_WORKING_DIRECTORY}\")\n"
      "\n"
      "set(SKIP_CLEAN_OVERALL_WORKING_DIRECTORY \"${PARSE_SKIP_CLEAN_OVERALL_WORKING_DIRECTORY}\")\n"
      "\n"
      "set(FAIL_FAST ${PARSE_FAIL_FAST})\n"
      "\n"
      "set(SHOW_START_END_DATE_TIME ${${PROJECT_NAME}_SHOW_TEST_START_END_DATE_TIME})\n"
      "\n"
      "set(SHOW_MACHINE_LOAD ${${PROJECT_NAME}_SHOW_MACHINE_LOAD_IN_TEST})\n"
      "\n"
      "set(CATEGORIES ${PARSE_CATEGORIES})\n"
      "\n"
      "set(PROCESSORS ${MAX_NUM_PROCESSORS_USED})\n"
      "\n"
      "set(TIMEOUT ${TIMEOUT_USED})\n"
      "\n"
      "#\n"
      "# Test invocation\n"
      "#\n"
      "\n"
      "set(CMAKE_MODULE_PATH ${${PROJECT_NAME}_TRIBITS_DIR}/${TRIBITS_CMAKE_UTILS_DIR})\n"
      "\n"
      "include(DriveAdvancedTest)\n"
      "\n"
      "drive_advanced_test()\n"
      )
  
    if (TRIBITS_ADD_ADVANCED_TEST_UNITTEST)
      global_set(TRIBITS_ADD_ADVANCED_TEST_NUM_CMNDS ${NUM_CMNDS})
      # NOTE: This var only gets set if the advanced test gets added after
      # applying all of the various logic.  Therefore, unit tests should only
      # check this variable for being empty to determine that the test was not
      # added.
    endif()
  
    if (${PROJECT_NAME}_VERBOSE_CONFIGURE)
      print_var(TEST_SCRIPT_STR)
    endif()
  
    # Write the script file
  
    if (NOT TRIBITS_ADD_ADVANCED_TEST_SKIP_SCRIPT)
  
      if (${PROJECT_NAME}_VERBOSE_CONFIGURE)
        message("\nWriting file \"${TEST_SCRIPT_FILE}\" ...")
      endif()
  
      file( WRITE "${TEST_SCRIPT_FILE}"
        "${TEST_SCRIPT_STR}" )
  
    endif()


  else()

    global_set(TRIBITS_ADD_ADVANCED_TEST_NUM_CMNDS "")

  endif()

endfunction()
