Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow types as test section names #91

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 101 additions & 29 deletions cmake/cmake_test/add_section.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,36 @@ include_guard()


#[[[
# Adds a test section, should be called inside of a declared test function directly before declaring the section function.
# The NAME parameter will be populated as by set() with the generated section function name. Declare the section function using this generated name. Ex:
# Adds a test section, should be called inside of a
# declared test function directly before declaring the section function.
#
# A variable named :code:`CMAKETEST_SECTION` will be set in the
# calling scope that holds the section function ID. Use this variable
# to define the CMake function holding the section code. Ex:
#
# .. code-block:: cmake
#
# #This is inside of a declared test function
# ct_add_section(NAME this_section)
# function(${this_section})
# function(${CMAKETEST_SECTION})
# message(STATUS "This code will run in a test section")
# endfunction()
#
# Upon being executed, this function will check if the section should be executed.
# If it is not, ct_add_section() will generate an ID for the section function and sets the variable pointed to by the NAME parameter to it.
# It will also construct a new CTExecutionUnit instance to represent the section.
#
# If the section is supposed to be executed, ct_add_section() will call the ``execute`` member function of the CTExecutionUnit representing this section.
# Exceptions will be tracked while the function is being executed. After completion of the test, the test status will be output
# to the screen. The section subsections will then be executed, following this same flow until there are no more subsections.
# Additionally, the NAME parameter will be populated as by set() with the
# generated section function name. This is for backwards-compatibility
# purposes. Ex:
#
# If a section raises an exception when it is not expected to, it will be marked as a failing section and its subsections
# will not be executed, due to limitations in how CMake handles failures. However, sibling sections as well as
# other tests will continue to execute, and the failures will be aggregated and printed after all tests have been ran.
# .. code-block:: cmake
#
# #This is inside of a declared test function
# ct_add_section(NAME this_section)
# function(${this_section})
# message(STATUS "This code will run in a test section")
# endfunction()
#
# This behavior is considered deprecated, use the first form
# for new sections.
#
# Print length of pass/fail lines can be adjusted with the `PRINT_LENGTH` option.
#
Expand All @@ -47,11 +55,18 @@ include_guard()
# 3. Length set by ct_set_print_length()
# 4. Built-in default of 80.
#
# If a section raises an exception when it is not expected to,
# it will be marked as a failing section and its subsections
# will not be executed, due to limitations in how CMake handles failures.
# However, sibling sections as well as other tests
# will continue to execute, and the failures will be aggregated
# and printed after all tests have been ran.
#
# **Keyword Arguments**
#
# :keyword NAME: Required argument specifying the name variable of the section. Will set a variable with
# specified name containing the generated function ID to use.
# :type NAME: pointer
# :type NAME: str*
# :keyword EXPECTFAIL: Option indicating whether the section is expected to fail or not, if
# specified will cause test failure when no exceptions were caught and success
# upon catching any exceptions.
Expand All @@ -60,19 +75,43 @@ include_guard()
# print length of pass/fail output lines.
# :type PRINT_LENGTH: int
#
# .. seealso:: :func:`add_test.cmake.ct_add_test` for details on EXPECTFAIL.
# .. seealso:: :func:`~cmake_test/add_test.ct_add_test` for details on EXPECTFAIL.
#
# .. seealso:: :func:`~cmake_test/exec_tests.ct_exec_tests` for details on halting tests on exceptions.
#
# Implementation Details
# ----------------------
#
# Upon being executed, this function will check if the section should be executed.
# If it is not, this function will generate an ID for the section
# function and sets the variable pointed to by the NAME parameter to it.
# It will also construct a new :class:`~cmake_test/execution_unit.CTExecutionUnit`
# instance to represent the section.
#
# If the section is supposed to be executed, this function
# will call the :meth:`~cmake_test/execution_unit.CTExecutionUnit.execute`
# method of the unit representing this section.
# Exceptions will be tracked while the function is being executed.
# After completion of the test, the test status will be output
# using :meth:`~cmake_test/execution_unit.CTExecutionUnit.print_pass_or_fail`.
# The section's subsections will then be executed recursively, following
# this same flow until there are no more subsections.
#
# .. seealso:: :func:`exec_test.cmake.ct_exec_test` for details on halting tests on exceptions.
#
#]]
function(ct_add_section)

#####################################
# Context switch and arg parsing #
#####################################

# Set debug mode to what it should be for cmaketest, in case the test changed it
set(_as_temp_debug_mode "${CMAKEPP_LANG_DEBUG_MODE}")
cpp_get_global(_as_ct_debug_mode "CT_DEBUG_MODE")
set(CMAKEPP_LANG_DEBUG_MODE "${_as_ct_debug_mode}")
cpp_set_global("CT_CURR_TEST_DEBUG_MODE" "${_as_temp_debug_mode}")

# Parse the arguments
cpp_get_global(_as_curr_instance "CT_CURRENT_EXECUTION_UNIT_INSTANCE")
CTExecutionUnit(GET "${_as_curr_instance}" _as_parent_print_length print_length)
CTExecutionUnit(GET "${_as_curr_instance}" _as_parent_print_length_forced print_length_forced)
Expand All @@ -83,7 +122,21 @@ function(ct_add_section)
cmake_parse_arguments(CT_ADD_SECTION "${_as_options}" "${_as_one_value_args}"
"${_as_multi_value_args}" ${ARGN} )

# This is to set a default value for the print length
# argument to prevent any weird empty strings from getting through
if(NOT DEFINED CT_ADD_SECTION_PRINT_LENGTH)
set(CT_ADD_SECTION_PRINT_LENGTH 0)
endif()


# Assert sig doesn't work too well with the position agnostic kwargs,
# so first we parse the arguments and then we check their types.
# Right now, allow any type for the name
cpp_assert_signature("${CT_ADD_SECTION_NAME};${CT_ADD_SECTION_PRINT_LENGTH}" str int)

#####################################
# Print length detection #
#####################################

set(_as_print_length_forced "NO")

Expand All @@ -97,38 +150,57 @@ function(ct_add_section)
set(_as_print_length "${_as_parent_print_length}")
endif()


#####################################
# Name retrieval and generation #
#####################################

CTExecutionUnit(GET "${_as_curr_instance}" _as_sibling_sections_map section_names_to_ids)
cpp_map(GET "${_as_sibling_sections_map}" _as_curr_section_id "${CT_ADD_SECTION_NAME}")

#Unset in main interpreter, TRUE in subprocess
cpp_get_global(_as_exec_expectfail "CT_EXEC_EXPECTFAIL")

# The name is set to "_" in the expectfail subprocess
# if the test is not intended to be ran

if(_as_exec_expectfail)
if("${${CT_ADD_SECTION_NAME}}" STREQUAL "" OR "${${CT_ADD_SECTION_NAME}}" STREQUAL "_")
set("${CT_ADD_SECTION_NAME}" "_" PARENT_SCOPE)
set(CMAKETEST_SECTION "_" PARENT_SCOPE)
# Reset debug mode in case test changed it
set(CMAKEPP_LANG_DEBUG_MODE "${_as_temp_debug_mode}")
return() #If section is not part of the call tree, immediately return
endif()
endif()

# Check if the name for this section is set,
# if so then retrieve it, else generation

CTExecutionUnit(GET "${_as_curr_instance}" _as_siblings section_names_to_ids)
cpp_map(HAS_KEY "${_as_siblings}" _as_unit_created "${CT_ADD_SECTION_NAME}")
if(NOT _as_unit_created)
cpp_unique_id(_as_section_name) #Generate random section ID

# Need to duplicate because CMake scoping rules are inconsistent.
# Setting in parent scope does not set in current scope
set("${CT_ADD_SECTION_NAME}" "${_as_section_name}")
set("${CT_ADD_SECTION_NAME}" "${_as_section_name}" PARENT_SCOPE)
else()
cpp_map(GET "${_as_siblings}" _as_section_name "${CT_ADD_SECTION_NAME}")
set("${CT_ADD_SECTION_NAME}" "${_as_section_name}")
set("${CT_ADD_SECTION_NAME}" "${_as_section_name}" PARENT_SCOPE)
endif()

#Get whether we should execute section now

# Set the output variables

# Need to duplicate because CMake scoping rules are inconsistent.
# Setting in parent scope does not set in current scope
set("${CT_ADD_SECTION_NAME}" "${_as_section_name}")
set("${CT_ADD_SECTION_NAME}" "${_as_section_name}" PARENT_SCOPE)

set("CMAKETEST_SECTION" "${_as_section_name}")
set("CMAKETEST_SECTION" "${_as_section_name}" PARENT_SCOPE)

#####################################
# Execution and unit construction #
#####################################

# Get whether we should execute section now
CTExecutionUnit(GET "${_as_curr_instance}" _as_exec_section execute_sections)

if(_as_exec_section) #Time to execute our section
Expand All @@ -142,9 +214,9 @@ function(ct_add_section)
CTExecutionUnit(GET "${_as_curr_instance}" _as_siblings section_names_to_ids)
cpp_map(HAS_KEY "${_as_siblings}" _as_unit_created "${CT_ADD_SECTION_NAME}")
if(NOT _as_unit_created)
#First time run, construct and configure
#the new section unit, as well as add it
#to its parent
# First time run, construct and configure
# the new section unit, as well as add it
# to its parent



Expand All @@ -156,7 +228,7 @@ function(ct_add_section)
CTExecutionUnit(
CTOR
_as_new_section
"${${CT_ADD_SECTION_NAME}}"
"${CMAKETEST_SECTION}"
"${CT_ADD_SECTION_NAME}"
"${CT_ADD_SECTION_EXPECTFAIL}"
)
Expand All @@ -166,9 +238,9 @@ function(ct_add_section)
CTExecutionUnit(SET "${_as_new_section}" test_file "${_as_parent_file}")
CTExecutionUnit(SET "${_as_new_section}" section_depth "${_as_new_section_depth}")
CTExecutionUnit(SET "${_as_new_section}" debug_mode "${_as_temp_debug_mode}")
CTExecutionUnit(append_child "${_as_curr_instance}" "${${CT_ADD_SECTION_NAME}}" "${_as_new_section}")
CTExecutionUnit(append_child "${_as_curr_instance}" "${CMAKETEST_SECTION}" "${_as_new_section}")
CTExecutionUnit(GET "${_as_curr_instance}" _as_siblings section_names_to_ids)
cpp_map(SET "${_as_siblings}" "${CT_ADD_SECTION_NAME}" "${${CT_ADD_SECTION_NAME}}")
cpp_map(SET "${_as_siblings}" "${CT_ADD_SECTION_NAME}" "${CMAKETEST_SECTION}")

endif()
# Reset debug mode in case test changed it
Expand Down
31 changes: 27 additions & 4 deletions cmake/cmake_test/add_test.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,33 @@ include_guard()
#
# Unit testing in CMakeTest works by `include()`ing each unit test, determining which ones
# need to be ran, and then executing them sequentially in-process. This macro defines which functions
# are to be interpretted as actual unit tests. It does so by setting a variable in the calling scope
# that is to be used as the function identifier. For example:
# are to be interpretted as actual unit tests.
#
# A variable named :code:`CMAKETEST_TEST` will be set in the
# calling scope that holds the test function ID. Use this variable
# to define the CMake function holding the section code. Ex:
#
# .. code-block:: cmake
#
# ct_add_test(NAME [=[Any test name here]=])
# function(${CMAKETEST_TEST})
# message(STATUS "This code will run in a test")
# endfunction()
#
#
# Additionally, the NAME parameter will be populated as by set() with the
# generated section function name. This is for backwards-compatibility
# purposes. Ex:
#
# .. code-block:: cmake
#
# ct_add_test(NAME this_test)
# function(${this_test})
# message(STATUS "This code will run in a unit test")
# message(STATUS "This code will run in a test")
# endfunction()
#
# This helps tests avoid name collisions and also allows the testing framework to keep track of them.
# This behavior is considered deprecated, use the first form
# for new tests.
#
# Print length of pass/fail lines can be adjusted with the `PRINT_LENGTH` option.
#
Expand All @@ -53,6 +69,11 @@ include_guard()
#
#]]
macro(ct_add_test)

#####################################
# Context switch and arg parsing #
#####################################

# Set debug mode to what it should be for cmaketest, in case the test changed it
set(_at_temp_debug_mode "${CMAKEPP_LANG_DEBUG_MODE}")
cpp_get_global(_at_ct_debug_mode "CT_DEBUG_MODE")
Expand All @@ -79,6 +100,7 @@ macro(ct_add_test)

if(_at_exec_expectfail AND ("${${CT_ADD_TEST_NAME}}" STREQUAL "" OR "${${CT_ADD_TEST_NAME}}" STREQUAL "_"))
set("${CT_ADD_TEST_NAME}" "_")
set(CMAKETEST_TEST "_" PARENT_SCOPE)
# Reset debug mode in case test changed it
set(CMAKEPP_LANG_DEBUG_MODE "${_at_temp_debug_mode}")
else()
Expand All @@ -96,6 +118,7 @@ macro(ct_add_test)

if(_at_old_id STREQUAL "")
cpp_unique_id("${CT_ADD_TEST_NAME}")
set(CMAKETEST_TEST "${${CT_ADD_TEST_NAME}}")
endif()

if(_at_exec_expectfail OR ("${_at_old_id}" STREQUAL ""))
Expand Down
4 changes: 2 additions & 2 deletions cmake/cmake_test/detail_/utilities/print_result.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ endfunction()
# :type print_length: int
#]]
function(_ct_print_pass _pp_name _pp_depth _pp_print_length)
cpp_assert_signature("${ARGV}" desc int int)
cpp_assert_signature("${ARGV}" str int int)
_ct_print_result("${_pp_name}" "${CT_BoldGreen}PASSED${CT_ColorReset}" "${_pp_depth}" "${_pp_print_length}")
endfunction()

Expand All @@ -124,6 +124,6 @@ endfunction()
# :type print_length: int
#]]
function(_ct_print_fail _pf_name _pf_depth _pf_print_length)
cpp_assert_signature("${ARGV}" desc int int)
cpp_assert_signature("${ARGV}" str int int)
_ct_print_result("${_pf_name}" "${CT_BoldRed}FAILED${CT_ColorReset}" "${_pf_depth}" "${_pf_print_length}")
endfunction()
4 changes: 2 additions & 2 deletions cmake/cmake_test/execution_unit.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,9 @@ cpp_class(CTExecutionUnit)
# :param expect_fail: Whether this unit is expected to fail.
#
#]]
cpp_constructor(CTOR CTExecutionUnit str desc* bool)
cpp_constructor(CTOR CTExecutionUnit str str bool)
function("${CTOR}" self test_id friendly_name expect_fail)
# Name could be a description or a function because it
# Name could be a description, a type, or a function because it
# isn't considered invalid to do so, such as using
# a test name of "set"
#
Expand Down
21 changes: 15 additions & 6 deletions docs/source/writing_tests/basic_test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,21 @@ a particular string is printed.

The :obj:`~cmake_test/add_test.ct_add_test` call tells CMakeTest that there is a test
with the name :code:`hello_world`. The function definition below it
defines the test. Note the odd :code:`${hello_world}` used as the
function name. This is required to link the function definition with
the :code:`ct_add_test()` call. :code:`ct_add_test()` takes the test name
and assigns a unique identifier to it to keep track of the accompanying
definition. Thus, if you use the same name for multiple tests in the same
scope they will conflict.
defines the test. Note the odd :code:`${CMAKETEST_TEST}` as the function name.
This is required to link the function definition with the test. The
value of the implicitly set :code:`CMAKETEST_TEST` variable is a unique
identifier for the test, it's only used internally.

The name of the test may be any string and can include special characters.
If the name has such special characters, pass it as a bracket argument
instead of as a quoted argument.

.. note::
There is a secondary, deprecated form of naming tests and sections.
This secondary form has restrictions on the name given such that it
is a valid CMake variable identifier. New tests and sections should
use the above form instead. See :obj:`~cmake_test/add_test.ct_add_test`
for information on the deprecated form.

Inside the function one will notice a call to
:obj:`~cmake_test/asserts/prints.ct_assert_prints`.
Expand Down
Loading
Loading