diff --git a/CMakeLists.txt b/CMakeLists.txt index 251c1ca..49b154a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,8 @@ catkin_package( catkin-pip-setup.cmake catkin-pip-prefix.cmake catkin-pip-requirements.cmake + test/pytest.cmake + test/nosetests.cmake ) # to be found in devel space diff --git a/cmake/catkin-pip-fixups.req b/cmake/catkin-pip-fixups.req index 81dcbf8..2cb636d 100644 --- a/cmake/catkin-pip-fixups.req +++ b/cmake/catkin-pip-fixups.req @@ -5,6 +5,10 @@ # Upgrading to use latest nose nose +# Also install pytest since we support it +pytest +pytest-cov +pytest-timeout # Fixing security since python 2.7.6 on trusty is broken : https://stackoverflow.com/questions/29099404/ssl-insecureplatform-error-when-using-requests-package # On trusty it seems that installing libgnutls28-dev fixes the issue... diff --git a/cmake/catkin-pip-prefix.cmake.in b/cmake/catkin-pip-prefix.cmake.in index 52b5cfb..1578b6d 100644 --- a/cmake/catkin-pip-prefix.cmake.in +++ b/cmake/catkin-pip-prefix.cmake.in @@ -92,12 +92,20 @@ macro(catkin_pip_setup_prefix ws_prefix) # Providing another catkin nosetests usage... # now we can finally use the simple "nosetests" entry_point (forcing cmake to find it) - find_program( PIP_NOSETESTS NAMES nosetests PATHS ${ws_prefix}/@CATKIN_GLOBAL_BIN_DESTINATION@ NO_DEFAULT_PATH) + find_program( PIP_NOSETESTS NAMES "nosetests" "nosetests-${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}" PATHS ${ws_prefix}/@CATKIN_GLOBAL_BIN_DESTINATION@ NO_DEFAULT_PATH) if(PIP_NOSETESTS) message( STATUS "Found catkin_pip nosetests command at ${PIP_NOSETESTS}.") else() message( FATAL_ERROR "catkin_pip nosetests command not found in ${ws_prefix}/@CATKIN_GLOBAL_BIN_DESTINATION@. Make sure you have installed the nose pip package on your ${ws_prefix} workspace.") endif() + # Same for py.test + find_program( PIP_PYTEST NAMES "py.test" "py.test-${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}" PATHS ${ws_prefix}/@CATKIN_GLOBAL_BIN_DESTINATION@ NO_DEFAULT_PATH) + if(PIP_PYTEST) + message( STATUS "Found catkin_pip py.test command at ${PIP_PYTEST}.") + else() + message( FATAL_ERROR "catkin_pip py.test command not found in ${ws_prefix}/@CATKIN_GLOBAL_BIN_DESTINATION@. Make sure you have installed the pytest pip package on your ${ws_prefix} workspace.") + endif() + endif() endmacro() \ No newline at end of file diff --git a/cmake/catkin-pip.cmake.in b/cmake/catkin-pip.cmake.in index d662764..d0aa75b 100644 --- a/cmake/catkin-pip.cmake.in +++ b/cmake/catkin-pip.cmake.in @@ -11,13 +11,13 @@ message(STATUS "Loading catkin-pip.cmake from ${CMAKE_CURRENT_LIST_DIR}... ") # protecting against missing cmake file dependency include ( "${CMAKE_CURRENT_LIST_DIR}/catkin-pip-requirements.cmake" RESULT_VARIABLE CATKIN_PIP_REQUIREMENTS_FOUND ) IF ( NOT CATKIN_PIP_REQUIREMENTS_FOUND ) - message ( FATAL_ERROR "{CMAKE_CURRENT_LIST_DIR}/catkin-pip-requirements.cmake Not Found !!!" ) + message ( FATAL_ERROR "${CMAKE_CURRENT_LIST_DIR}/catkin-pip-requirements.cmake Not Found !!!" ) ENDIF ( NOT CATKIN_PIP_REQUIREMENTS_FOUND ) # protecting against missing cmake file dependency include ( "${CMAKE_CURRENT_LIST_DIR}/catkin-pip-setup.cmake" RESULT_VARIABLE CATKIN_PIP_SETUP_FOUND ) IF ( NOT CATKIN_PIP_SETUP_FOUND ) - message ( FATAL_ERROR "{CMAKE_CURRENT_LIST_DIR}/catkin-pip-setup.cmake Not Found !!!" ) + message ( FATAL_ERROR "${CMAKE_CURRENT_LIST_DIR}/catkin-pip-setup.cmake Not Found !!!" ) ENDIF ( NOT CATKIN_PIP_SETUP_FOUND ) catkin_add_env_hooks(42.site-packages SHELLS bash DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/../env-hooks) diff --git a/cmake/test/nosetests.cmake.in b/cmake/test/nosetests.cmake.in new file mode 100644 index 0000000..24d6804 --- /dev/null +++ b/cmake/test/nosetests.cmake.in @@ -0,0 +1,120 @@ +_generate_function_if_testing_is_disabled("catkin_add_nosetests") + +# +# Add Python nose tests. +# +# Nose collects tests from the directory ``dir`` automatically. +# +# .. note:: The test can be executed by calling ``nosetests`` +# directly or using: +# `` make run_tests_${PROJECT_NAME}_nosetests_${dir}`` +# (where slashes in the ``dir`` are replaced with periods) +# +# :param path: a relative or absolute directory to search for +# nosetests in or a relative or absolute file containing tests +# :type path: string +# :param DEPENDENCIES: the targets which must be built before executing +# the test +# :type DEPENDENCIES: list of strings +# :param TIMEOUT: the timeout for individual tests in seconds +# (default: 60) +# :type TIMEOUT: integer +# :param WORKING_DIRECTORY: the working directory when executing the +# tests (this option can only be used when the ``path`` argument is a +# file but not when it is a directory) +# :type WORKING_DIRECTORY: string +# +# @public +# +function(catkin_add_nosetests path) + _warn_if_skip_testing("catkin_add_nosetests") + + if(NOT PIP_NOSETESTS) + message(STATUS "skipping nosetests(${path}) in project '${PROJECT_NAME}'") + return() + endif() + + cmake_parse_arguments(_nose "" "TIMEOUT;WORKING_DIRECTORY" "DEPENDENCIES" ${ARGN}) + if(NOT _nose_TIMEOUT) + set(_nose_TIMEOUT 60) + endif() + if(NOT _nose_TIMEOUT GREATER 0) + message(FATAL_ERROR "nosetests() TIMEOUT argument must be a valid number of seconds greater than zero") + endif() + + # check that the directory exists + set(_path_name _path_name-NOTFOUND) + if(IS_ABSOLUTE ${path}) + set(_path_name ${path}) + else() + find_file(_path_name ${path} + PATHS ${CMAKE_CURRENT_SOURCE_DIR} + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) + if(NOT _path_name) + message(FATAL_ERROR "Can't find nosetests path '${path}'") + endif() + endif() + + # check if coverage reports are being requested + if("$ENV{CATKIN_TEST_COVERAGE}" STREQUAL "1") + set(_covarg " --with-coverage") + endif() + + # strip PROJECT_SOURCE_DIR and PROJECT_BINARY_DIR prefix from output_file_name + set(output_file_name ${path}) + _strip_path_prefix(output_file_name "${output_file_name}" "${PROJECT_SOURCE_DIR}") + _strip_path_prefix(output_file_name "${output_file_name}" "${PROJECT_BINARY_DIR}") + if("${output_file_name}" STREQUAL "") + set(output_file_name ".") + endif() + string(REPLACE "/" "." output_file_name ${output_file_name}) + string(REPLACE ":" "." output_file_name ${output_file_name}) + + set(output_path ${CATKIN_TEST_RESULTS_DIR}/${PROJECT_NAME}) + # make --xunit-file argument an absolute path (https://github.com/nose-devs/nose/issues/779) + get_filename_component(output_path "${output_path}" ABSOLUTE) + set(cmd "${CMAKE_COMMAND} -E make_directory ${output_path}") + if(IS_DIRECTORY ${_path_name}) + set(tests "--where=${_path_name}") + else() + set(tests "${_path_name}") + endif() + set(cmd ${cmd} "${PIP_NOSETESTS} -P --process-timeout=${_nose_TIMEOUT} ${tests} --with-xunit --xunit-file=${output_path}/nosetests-${output_file_name}.xml${_covarg}") + catkin_run_tests_target("nosetests" ${output_file_name} "nosetests-${output_file_name}.xml" COMMAND ${cmd} DEPENDENCIES ${_nose_DEPENDENCIES} WORKING_DIRECTORY ${_nose_WORKING_DIRECTORY}) +endfunction() + +find_program(PIP_NOSETESTS NAMES + "nosetests${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}" + "nosetests-${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}" + "nosetests${PYTHON_VERSION_MAJOR}" + "nosetests-${PYTHON_VERSION_MAJOR}" + "nosetests") +if(PIP_NOSETESTS) + message(STATUS "Using Python nosetests: ${PIP_NOSETESTS}") +else() + if("${PYTHON_VERSION_MAJOR}" STREQUAL "3") + message(WARNING "nosetests not found, Python tests can not be run (try installing package 'python3-nose')") + else() + message(WARNING "nosetests not found, Python tests can not be run (try installing package 'python-nose')") + endif() +endif() + +macro(_strip_path_prefix var value prefix) + if("${value}" STREQUAL "${prefix}" OR "${value}" STREQUAL "${prefix}/") + set(${var} "") + else() + set(${var} "${value}") + string(LENGTH "${prefix}/" prefix_length) + string(LENGTH "${value}" var_length) + if(${var_length} GREATER ${prefix_length}) + string(SUBSTRING "${value}" 0 ${prefix_length} var_prefix) + if("${var_prefix}" STREQUAL "${prefix}/") + # passing length -1 does not work for CMake < 2.8.5 + # http://public.kitware.com/Bug/view.php?id=10740 + string(LENGTH "${value}" _rest) + math(EXPR _rest "${_rest} - ${prefix_length}") + string(SUBSTRING "${value}" ${prefix_length} ${_rest} ${var}) + endif() + endif() + endif() +endmacro() \ No newline at end of file diff --git a/cmake/test/pytest.cmake.in b/cmake/test/pytest.cmake.in new file mode 100644 index 0000000..1adfa7f --- /dev/null +++ b/cmake/test/pytest.cmake.in @@ -0,0 +1,101 @@ +_generate_function_if_testing_is_disabled("catkin_add_pytests") + +include ( "${CMAKE_CURRENT_LIST_DIR}/nosetests.cmake" RESULT_VARIABLE CATKIN_PIP_NOSETESTS_FOUND ) +IF ( NOT CATKIN_PIP_NOSETESTS_FOUND ) + message ( FATAL_ERROR "${CMAKE_CURRENT_LIST_DIR}/nosetests.cmake Not Found !!!" ) +ENDIF ( NOT CATKIN_PIP_NOSETESTS_FOUND ) + +# +# Add Python pytest tests. +# +# Nose collects tests from the directory ``dir`` automatically. +# +# .. note:: The test can be executed by calling ``py.test`` +# directly or using: +# `` make run_tests_${PROJECT_NAME}_pytests_${dir}`` +# (where slashes in the ``dir`` are replaced with periods) +# +# :param path: a relative or absolute directory to search for +# pytests in or a relative or absolute file containing tests +# :type path: string +# :param DEPENDENCIES: the targets which must be built before executing +# the test +# :type DEPENDENCIES: list of strings +# :param TIMEOUT: the timeout for individual tests in seconds +# (default: 60) +# :type TIMEOUT: integer +# :param WORKING_DIRECTORY: the working directory when executing the +# tests (this option can only be used when the ``path`` argument is a +# file but not when it is a directory) +# :type WORKING_DIRECTORY: string +# +# @public +# +function(catkin_add_pytests path) + _warn_if_skip_testing("catkin_add_pytests") + + if(NOT PIP_PYTEST) + message(STATUS "skipping pytests(${path}) in project '${PROJECT_NAME}'") + return() + endif() + + cmake_parse_arguments(_pytest "" "TIMEOUT;WORKING_DIRECTORY" "DEPENDENCIES" ${ARGN}) + if(NOT _pytest_TIMEOUT) + set(_pytest_TIMEOUT 60) + endif() + if(NOT _pytest_TIMEOUT GREATER 0) + message(FATAL_ERROR "py.test() TIMEOUT argument must be a valid number of seconds greater than zero") + endif() + + # check that the directory exists + set(_path_name _path_name-NOTFOUND) + if(IS_ABSOLUTE ${path}) + set(_path_name ${path}) + else() + find_file(_path_name ${path} + PATHS ${CMAKE_CURRENT_SOURCE_DIR} + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) + if(NOT _path_name) + message(FATAL_ERROR "Can't find pytests path '${path}'") + endif() + endif() + + # check if coverage reports are being requested + if("$ENV{CATKIN_TEST_COVERAGE}" STREQUAL "1") + set(_covarg "--cov-report xml --cov-report annotate --cov=${PROJECT_SOURCE_DIR}") + endif() + + # strip PROJECT_SOURCE_DIR and PROJECT_BINARY_DIR prefix from output_file_name + set(output_file_name ${path}) + _strip_path_prefix(output_file_name "${output_file_name}" "${PROJECT_SOURCE_DIR}") + _strip_path_prefix(output_file_name "${output_file_name}" "${PROJECT_BINARY_DIR}") + if("${output_file_name}" STREQUAL "") + set(output_file_name ".") + endif() + string(REPLACE "/" "." output_file_name ${output_file_name}) + string(REPLACE ":" "." output_file_name ${output_file_name}) + + set(output_path ${CATKIN_TEST_RESULTS_DIR}/${PROJECT_NAME}) + # make --xunit-file argument an absolute path (https://github.com/nose-devs/nose/issues/779) + get_filename_component(output_path "${output_path}" ABSOLUTE) + set(cmd "${CMAKE_COMMAND} -E make_directory ${output_path}") + set(tests "${_path_name}") + set(cmd ${cmd} "${PY_TEST} --timeout=${_pytest_TIMEOUT} ${tests} --junit-xml ${output_path}/pytests-${output_file_name}.xml ${_covarg}") + catkin_run_tests_target("pytests" ${output_file_name} "pytests-${output_file_name}.xml" COMMAND ${cmd} DEPENDENCIES ${_pytest_DEPENDENCIES} WORKING_DIRECTORY ${_pytest_WORKING_DIRECTORY}) +endfunction() + +find_program(PY_TEST NAMES + "py.test${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}" + "py.test-${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}" + "py.test${PYTHON_VERSION_MAJOR}" + "py.test-${PYTHON_VERSION_MAJOR}" + "py.test") +if(PY_TEST) + message(STATUS "Using Python py.test: ${PY_TEST}") +else() + if("${PYTHON_VERSION_MAJOR}" STREQUAL "3") + message(WARNING "py.test not found, Python tests can not be run (try installing package 'python3-pytest')") + else() + message(WARNING "py.test not found, Python tests can not be run (try installing package 'python-pytest')") + endif() +endif() diff --git a/test/pipproject/CMakeLists.txt b/test/pipproject/CMakeLists.txt index 1b5d2ea..32ef48c 100644 --- a/test/pipproject/CMakeLists.txt +++ b/test/pipproject/CMakeLists.txt @@ -28,4 +28,5 @@ catkin_package() ## Unit tests if (CATKIN_ENABLE_TESTING) catkin_add_nosetests(${PIP_PROJECT_DIR}/tests) + catkin_add_pytests(${PIP_PROJECT_DIR}/tests) endif()