Home | History | Annotate | Download | only in contrib
      1 #==================================================================================================#
      2 #  supported macros                                                                                #
      3 #    - TEST_CASE,                                                                                  #
      4 #    - SCENARIO,                                                                                   #
      5 #    - TEST_CASE_METHOD,                                                                           #
      6 #    - CATCH_TEST_CASE,                                                                            #
      7 #    - CATCH_SCENARIO,                                                                             #
      8 #    - CATCH_TEST_CASE_METHOD.                                                                     #
      9 #                                                                                                  #
     10 #  Usage                                                                                           #
     11 # 1. make sure this module is in the path or add this otherwise:                                   #
     12 #    set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/")              #
     13 # 2. make sure that you've enabled testing option for the project by the call:                     #
     14 #    enable_testing()                                                                              #
     15 # 3. add the lines to the script for testing target (sample CMakeLists.txt):                       #
     16 #        project(testing_target)                                                                   #
     17 #        set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/")          #
     18 #        enable_testing()                                                                          #
     19 #                                                                                                  #
     20 #        find_path(CATCH_INCLUDE_DIR "catch.hpp")                                                  #
     21 #        include_directories(${INCLUDE_DIRECTORIES} ${CATCH_INCLUDE_DIR})                          #
     22 #                                                                                                  #
     23 #        file(GLOB SOURCE_FILES "*.cpp")                                                           #
     24 #        add_executable(${PROJECT_NAME} ${SOURCE_FILES})                                           #
     25 #                                                                                                  #
     26 #        include(ParseAndAddCatchTests)                                                            #
     27 #        ParseAndAddCatchTests(${PROJECT_NAME})                                                    #
     28 #                                                                                                  #
     29 # The following variables affect the behavior of the script:                                       #
     30 #                                                                                                  #
     31 #    PARSE_CATCH_TESTS_VERBOSE (Default OFF)                                                       #
     32 #    -- enables debug messages                                                                     #
     33 #    PARSE_CATCH_TESTS_NO_HIDDEN_TESTS (Default OFF)                                               #
     34 #    -- excludes tests marked with [!hide], [.] or [.foo] tags                                     #
     35 #    PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME (Default ON)                                       #
     36 #    -- adds fixture class name to the test name                                                   #
     37 #    PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME (Default ON)                                        #
     38 #    -- adds cmake target name to the test name                                                    #
     39 #    PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF)                                      #
     40 #    -- causes CMake to rerun when file with tests changes so that new tests will be discovered    #
     41 #                                                                                                  #
     42 # One can also set (locally) the optional variable OptionalCatchTestLauncher to precise the way    #
     43 # a test should be run. For instance to use test MPI, one can write                                #
     44 #     set(OptionalCatchTestLauncher ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${NUMPROC})                 #
     45 # just before calling this ParseAndAddCatchTests function                                          #
     46 #                                                                                                  #
     47 #==================================================================================================#
     48 
     49 cmake_minimum_required(VERSION 2.8.8)
     50 
     51 option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF)
     52 option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF)
     53 option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the test name" ON)
     54 option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON)
     55 option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF)
     56 
     57 function(PrintDebugMessage)
     58     if(PARSE_CATCH_TESTS_VERBOSE)
     59             message(STATUS "ParseAndAddCatchTests: ${ARGV}")
     60     endif()
     61 endfunction()
     62 
     63 # This removes the contents between
     64 #  - block comments (i.e. /* ... */)
     65 #  - full line comments (i.e. // ... )
     66 # contents have been read into '${CppCode}'.
     67 # !keep partial line comments
     68 function(RemoveComments CppCode)
     69   string(ASCII 2 CMakeBeginBlockComment)
     70   string(ASCII 3 CMakeEndBlockComment)
     71   string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}")
     72   string(REGEX REPLACE "\\*/" "${CMakeEndBlockComment}" ${CppCode} "${${CppCode}}")
     73   string(REGEX REPLACE "${CMakeBeginBlockComment}[^${CMakeEndBlockComment}]*${CMakeEndBlockComment}" "" ${CppCode} "${${CppCode}}")
     74   string(REGEX REPLACE "\n[ \t]*//+[^\n]+" "\n" ${CppCode} "${${CppCode}}")
     75 
     76   set(${CppCode} "${${CppCode}}" PARENT_SCOPE)
     77 endfunction()
     78 
     79 # Worker function
     80 function(ParseFile SourceFile TestTarget)
     81     # According to CMake docs EXISTS behavior is well-defined only for full paths.
     82     get_filename_component(SourceFile ${SourceFile} ABSOLUTE)
     83     if(NOT EXISTS ${SourceFile})
     84         message(WARNING "Cannot find source file: ${SourceFile}")
     85         return()
     86     endif()
     87     PrintDebugMessage("parsing ${SourceFile}")
     88     file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME)
     89 
     90     # Remove block and fullline comments
     91     RemoveComments(Contents)
     92 
     93     # Find definition of test names
     94     string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^\)]+\\)+[ \t\n]*{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}")
     95 
     96     if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests)
     97       PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property")
     98       set_property(
     99         DIRECTORY
    100         APPEND
    101         PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile}
    102       )
    103     endif()
    104 
    105     foreach(TestName ${Tests})
    106         # Strip newlines
    107         string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}")
    108 
    109         # Get test type and fixture if applicable
    110         string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}")
    111         string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}")
    112         string(REGEX REPLACE "${TestType}\\([ \t]*" "" TestFixture "${TestTypeAndFixture}")
    113 
    114         # Get string parts of test definition
    115         string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}")
    116 
    117         # Strip wrapping quotation marks
    118         string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}")
    119         string(REPLACE "\";\"" ";" TestStrings "${TestStrings}")
    120 
    121         # Validate that a test name and tags have been provided
    122         list(LENGTH TestStrings TestStringsLength)
    123         if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1)
    124             message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}")
    125         endif()
    126 
    127         # Assign name and tags
    128         list(GET TestStrings 0 Name)
    129         if("${TestType}" STREQUAL "SCENARIO")
    130             set(Name "Scenario: ${Name}")
    131         endif()
    132         if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND TestFixture)
    133             set(CTestName "${TestFixture}:${Name}")
    134         else()
    135             set(CTestName "${Name}")
    136         endif()
    137         if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME)
    138             set(CTestName "${TestTarget}:${CTestName}")
    139         endif()
    140         # add target to labels to enable running all tests added from this target
    141         set(Labels ${TestTarget})
    142         if(TestStringsLength EQUAL 2)
    143             list(GET TestStrings 1 Tags)
    144             string(TOLOWER "${Tags}" Tags)
    145             # remove target from labels if the test is hidden
    146             if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*")
    147                 list(REMOVE_ITEM Labels ${TestTarget})
    148             endif()
    149             string(REPLACE "]" ";" Tags "${Tags}")
    150             string(REPLACE "[" "" Tags "${Tags}")
    151         else()
    152           # unset tags variable from previous loop
    153           unset(Tags)
    154         endif()
    155 
    156         list(APPEND Labels ${Tags})
    157 
    158         list(FIND Labels "!hide" IndexOfHideLabel)
    159         set(HiddenTagFound OFF)
    160         foreach(label ${Labels})
    161             string(REGEX MATCH "^!hide|^\\." result ${label})
    162             if(result)
    163                 set(HiddenTagFound ON)
    164                 break()
    165             endif(result)
    166         endforeach(label)
    167         if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_LESS "3.9")
    168             PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label")
    169         else()
    170             PrintDebugMessage("Adding test \"${CTestName}\"")
    171             if(Labels)
    172                 PrintDebugMessage("Setting labels to ${Labels}")
    173             endif()
    174 
    175             # Escape commas in the test spec
    176             string(REPLACE "," "\\," Name ${Name})
    177 
    178             # Add the test and set its properties
    179             add_test(NAME "\"${CTestName}\"" COMMAND ${OptionalCatchTestLauncher} ${TestTarget} ${Name} ${AdditionalCatchParameters})
    180             # Old CMake versions do not document VERSION_GREATER_EQUAL, so we use VERSION_GREATER with 3.8 instead
    181             if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_GREATER "3.8")
    182                 PrintDebugMessage("Setting DISABLED test property")
    183                 set_tests_properties("\"${CTestName}\"" PROPERTIES DISABLED ON)
    184             else()
    185                 set_tests_properties("\"${CTestName}\"" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran"
    186                                                         LABELS "${Labels}")
    187             endif()
    188         endif()
    189 
    190 
    191     endforeach()
    192 endfunction()
    193 
    194 # entry point
    195 function(ParseAndAddCatchTests TestTarget)
    196     PrintDebugMessage("Started parsing ${TestTarget}")
    197     get_target_property(SourceFiles ${TestTarget} SOURCES)
    198     PrintDebugMessage("Found the following sources: ${SourceFiles}")
    199     foreach(SourceFile ${SourceFiles})
    200         ParseFile(${SourceFile} ${TestTarget})
    201     endforeach()
    202     PrintDebugMessage("Finished parsing ${TestTarget}")
    203 endfunction()
    204