Skip to content
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
45 changes: 35 additions & 10 deletions cmake/FindPytest.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,34 @@ if (Pytest_FOUND AND NOT TARGET Pytest::Pytest)

# Function to discover pytest tests and add them to CTest.
function(pytest_discover_tests NAME)
set(_BOOL_ARGS
STRIP_PARAM_BRACKETS
INCLUDE_FILE_PATH
BUNDLE_TESTS
)

set(_SINGLE_VALUE_ARGS
WORKING_DIRECTORY
TRIM_FROM_NAME
TRIM_FROM_FULL_NAME
)

set(_MULTI_VALUE_ARGS
TEST_PATHS
LIBRARY_PATH_PREPEND
PYTHON_PATH_PREPEND
ENVIRONMENT
PROPERTIES
DEPENDS
EXTRA_ARGS
DISCOVERY_EXTRA_ARGS
)

cmake_parse_arguments(
PARSE_ARGV 1 "" "STRIP_PARAM_BRACKETS;INCLUDE_FILE_PATH;BUNDLE_TESTS"
"WORKING_DIRECTORY;TRIM_FROM_NAME;TRIM_FROM_FULL_NAME"
"LIBRARY_PATH_PREPEND;PYTHON_PATH_PREPEND;ENVIRONMENT;PROPERTIES;DEPENDS;EXTRA_ARGS;DISCOVERY_EXTRA_ARGS"
PARSE_ARGV 1 ""
"${_BOOL_ARGS}"
"${_SINGLE_VALUE_ARGS}"
"${_MULTI_VALUE_ARGS}"
)

# Set platform-specific library path environment variable.
Expand Down Expand Up @@ -119,6 +143,7 @@ if (Pytest_FOUND AND NOT TARGET Pytest::Pytest)
DEPENDS ${_DEPENDS}
COMMAND ${CMAKE_COMMAND}
-D "PYTEST_EXECUTABLE=${PYTEST_EXECUTABLE}"
-D "TEST_PATHS=${_TEST_PATHS}"
-D "TEST_GROUP_NAME=${NAME}"
-D "BUNDLE_TESTS=${_BUNDLE_TESTS}"
-D "LIBRARY_ENV_NAME=${LIBRARY_ENV_NAME}"
Expand All @@ -139,13 +164,13 @@ if (Pytest_FOUND AND NOT TARGET Pytest::Pytest)
# Create a custom target to run the tests.
add_custom_target(${NAME} ALL DEPENDS ${_tests_file})

file(WRITE "${_include_file}"
"if(EXISTS \"${_tests_file}\")\n"
" include(\"${_tests_file}\")\n"
"else()\n"
" add_test(${NAME}_NOT_BUILT ${NAME}_NOT_BUILT)\n"
"endif()\n"
)
file(WRITE "${_include_file}"
"if(EXISTS \"${_tests_file}\")\n"
" include(\"${_tests_file}\")\n"
"else()\n"
" add_test(${NAME}_NOT_BUILT ${NAME}_NOT_BUILT)\n"
"endif()\n"
)

# Register the include file to be processed for tests.
set_property(DIRECTORY
Expand Down
35 changes: 25 additions & 10 deletions cmake/PytestAddTests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,21 @@ if(CMAKE_SCRIPT_MODE_FILE)
list(JOIN EXTRA_ARGS_WRAPPED " " EXTRA_ARGS_STR)

# Macro to create individual tests with optional test properties.
macro(create_test NAME IDENTIFIER)
string(APPEND _content
"add_test([==[${NAME}]==] \"${PYTEST_EXECUTABLE}\" [==[${IDENTIFIER}]==] ${EXTRA_ARGS_STR} )\n"
)
macro(create_test NAME IDENTIFIERS)
string(APPEND _content "add_test([==[${NAME}]==] \"${PYTEST_EXECUTABLE}\"")

foreach(identifier ${IDENTIFIERS})
string(APPEND _content " [==[${identifier}]==]")
endforeach()

string(APPEND _content " ${EXTRA_ARGS_STR} )\n")

# Prepare the properties for the test, including the environment settings.
set(args "PROPERTIES ENVIRONMENT [==[${ENCODED_ENVIRONMENT}]==]")

# Add working directory
string(APPEND args " WORKING_DIRECTORY [==[${WORKING_DIRECTORY}]==]")

# Append any additional properties, escaping complex characters if necessary.
foreach(property ${TEST_PROPERTIES})
if(property MATCHES "[^-./:a-zA-Z0-9_]")
Expand All @@ -61,27 +68,35 @@ if(CMAKE_SCRIPT_MODE_FILE)

# If tests are bundled together, create a single test group.
if (BUNDLE_TESTS)
create_test("\${TEST_GROUP_NAME}" "\${WORKING_DIRECTORY}")
create_test("\${TEST_GROUP_NAME}" "\${TEST_PATHS}")

else()
# Set environment variables for collecting tests.
set(ENV{${LIBRARY_ENV_NAME}} "${LIBRARY_PATH}")
set(ENV{PYTHONPATH} "${PYTHON_PATH}")
set(ENV{PYTHONWARNINGS} "ignore")

set(_command
"${PYTEST_EXECUTABLE}" --collect-only -q
"--rootdir=${WORKING_DIRECTORY}"
${DISCOVERY_EXTRA_ARGS}
)

foreach(test_path IN LISTS TEST_PATHS)
list(APPEND _command "${test_path}")
endforeach()

# Collect tests.
execute_process(
COMMAND "${PYTEST_EXECUTABLE}"
--collect-only -q
--rootdir=${WORKING_DIRECTORY} ${DISCOVERY_EXTRA_ARGS} .
COMMAND ${_command}
OUTPUT_VARIABLE _output_lines
ERROR_VARIABLE _output_lines
OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY ${WORKING_DIRECTORY}
)

# Check for errors during test collection.
string(REGEX MATCH "=+ ERRORS =+(.*)" _error "${_output_lines}")
string(REGEX MATCH "(=+ ERRORS =+|ERROR:).*" _error "${_output_lines}")

if (_error)
message(${_error})
Expand Down Expand Up @@ -141,7 +156,7 @@ if(CMAKE_SCRIPT_MODE_FILE)

# Prefix the test name with the test group name.
set(test_name "${TEST_GROUP_NAME}.${test_name}")
set(test_case "${WORKING_DIRECTORY}/${line}")
set(test_case "${line}")

# Create the test for CTest.
create_test("\${test_name}" "\${test_case}")
Expand Down
14 changes: 14 additions & 0 deletions doc/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ API Reference
with :term:`Pytest` within a controlled environment::

pytest_discover_tests(NAME
[TEST_PATHS path1 path2...]
[WORKING_DIRECTORY dir]
[TRIM_FROM_NAME pattern]
[TRIM_FROM_FULL_NAME pattern]
Expand All @@ -35,6 +36,19 @@ API Reference
used as a prefix for each test created, or as an identifier the bundled
test.

* ``TEST_PATHS``

Specifies a list of files or directories to search when executing
:term:`Pytest` from the current source directory (or from the
``WORKING_DIRECTORY`` value if provided)::

pytest_discover_tests(
...
TEST_PATHS
path1
path2/test.py
)

* ``WORKING_DIRECTORY``

Specify the directory in which to run the :term:`Pytest` command. If
Expand Down
19 changes: 19 additions & 0 deletions doc/release/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@ Release Notes

Added compatibility with CMake 4.1.

.. change:: new

Added ``TEST_PATHS`` option to the :func:`pytest_discover_tests`
function, allowing users to limit test discovery and execution to
specific files or directories. If not specified, ``TEST_PATHS``
defaults to the ``testpaths`` setting in :file:`pytest.ini`, or to the
current directory if ``testpaths`` is not set. This matches
:term:`Pytest`’s native behavior and preserves full backward
compatibility.

.. seealso::

`'testpaths' configuration option
<https://docs.pytest.org/en/stable/reference/reference.html#confval-testpaths>`_

.. change:: fixed

Updated discovery error detection to recognize both block-form and
single-line messages from :term:`Pytest`.

.. release:: 0.13.0
:date: 2025-02-16
Expand Down
12 changes: 6 additions & 6 deletions test/01-modify-name/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ add_test(NAME TestModifyName.Validate.Simple
COMMAND ${CMAKE_COMMAND}
-D "TEST_PREFIX=TestModifyName.Simple"
-D "EXPECTED=${EXPECTED}"
-P ${CMAKE_CURRENT_LIST_DIR}/compare_discovered_tests.cmake
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
)

pytest_discover_tests(TestModifyName.Bundled BUNDLE_TESTS)
add_test(NAME TestModifyName.Validate.Bundled
COMMAND ${CMAKE_COMMAND}
-D "TEST_PREFIX=TestModifyName.Bundled"
-D "EXPECTED=TestModifyName.Bundled"
-P ${CMAKE_CURRENT_LIST_DIR}/compare_discovered_tests.cmake
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
)

pytest_discover_tests(
Expand Down Expand Up @@ -64,7 +64,7 @@ add_test(NAME TestModifyName.Validate.TrimFromName
COMMAND ${CMAKE_COMMAND}
-D "TEST_PREFIX=TestModifyName.TrimFromName"
-D "EXPECTED=${EXPECTED}"
-P ${CMAKE_CURRENT_LIST_DIR}/compare_discovered_tests.cmake
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
)

pytest_discover_tests(
Expand Down Expand Up @@ -92,7 +92,7 @@ add_test(NAME TestModifyName.Validate.StripParamBrackets
COMMAND ${CMAKE_COMMAND}
-D "TEST_PREFIX=TestModifyName.StripParamBrackets"
-D "EXPECTED=${EXPECTED}"
-P ${CMAKE_CURRENT_LIST_DIR}/compare_discovered_tests.cmake
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
)

pytest_discover_tests(
Expand Down Expand Up @@ -120,7 +120,7 @@ add_test(NAME TestModifyName.Validate.IncludeFilePath
COMMAND ${CMAKE_COMMAND}
-D "TEST_PREFIX=TestModifyName.IncludeFilePath"
-D "EXPECTED=${EXPECTED}"
-P ${CMAKE_CURRENT_LIST_DIR}/compare_discovered_tests.cmake
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
)

pytest_discover_tests(
Expand Down Expand Up @@ -149,5 +149,5 @@ add_test(NAME TestModifyName.Validate.TrimFromFullName
COMMAND ${CMAKE_COMMAND}
-D "TEST_PREFIX=TestModifyName.TrimFromFullName"
-D "EXPECTED=${EXPECTED}"
-P ${CMAKE_CURRENT_LIST_DIR}/compare_discovered_tests.cmake
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
)
18 changes: 18 additions & 0 deletions test/07-working-directory/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.20)

project(TestWorkingDirectory)

find_package(Pytest REQUIRED)

enable_testing()

pytest_discover_tests(
TestWorkingDirectory
WORKING_DIRECTORY subdir
)

pytest_discover_tests(
TestWorkingDirectory.Bundled
WORKING_DIRECTORY subdir
BUNDLE_TESTS
)
2 changes: 2 additions & 0 deletions test/07-working-directory/subdir/test_correct.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test_addition():
assert 1 + 1 == 2
1 change: 1 addition & 0 deletions test/07-working-directory/test_incorrect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
raise RuntimeError("This test files should have been excluded")
94 changes: 94 additions & 0 deletions test/08-test-paths/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
cmake_minimum_required(VERSION 3.20)

project(TestCustomPaths)

find_package(Pytest REQUIRED)

enable_testing()

pytest_discover_tests(
TestCustomPaths.FromConfig
)
set(EXPECTED
"TestCustomPaths.FromConfig.test_addition"
"TestCustomPaths.FromConfig.test_concat"
"TestCustomPaths.FromConfig.test_power"
"TestCustomPaths.FromConfig.test_substraction"
"TestCustomPaths.FromConfig.test_upper"
)
add_test(NAME TestCustomPaths.Validate.FromConfig
COMMAND ${CMAKE_COMMAND}
-D "TEST_PREFIX=TestCustomPaths.FromConfig"
-D "EXPECTED=${EXPECTED}"
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
)

pytest_discover_tests(
TestCustomPaths.BundledFromConfig
BUNDLE_TESTS
)
set(EXPECTED
"TestCustomPaths.BundledFromConfig"
)
add_test(NAME TestCustomPaths.Validate.BundledFromConfig
COMMAND ${CMAKE_COMMAND}
-D "TEST_PREFIX=TestCustomPaths.BundledFromConfig"
-D "EXPECTED=${EXPECTED}"
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
)

pytest_discover_tests(
TestCustomPaths.FilePaths
TEST_PATHS
${CMAKE_CURRENT_LIST_DIR}/test_a/math/test_add.py
test_a/choice.py
test_b/test_concat.py
)
set(EXPECTED
"TestCustomPaths.FilePaths.test_addition"
"TestCustomPaths.FilePaths.test_concat"
"TestCustomPaths.FilePaths.test_random"
)
add_test(NAME TestCustomPaths.Validate.FilePaths
COMMAND ${CMAKE_COMMAND}
-D "TEST_PREFIX=TestCustomPaths.FilePaths"
-D "EXPECTED=${EXPECTED}"
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
)

pytest_discover_tests(
TestCustomPaths.DirPaths
TEST_PATHS
test_a
${CMAKE_CURRENT_LIST_DIR}/test_b/math
)
set(EXPECTED
"TestCustomPaths.DirPaths.test_addition"
"TestCustomPaths.DirPaths.test_power"
"TestCustomPaths.DirPaths.test_substraction"
"TestCustomPaths.DirPaths.test_upper"
)
add_test(NAME TestCustomPaths.Validate.DirPaths
COMMAND ${CMAKE_COMMAND}
-D "TEST_PREFIX=TestCustomPaths.DirPaths"
-D "EXPECTED=${EXPECTED}"
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
)

pytest_discover_tests(
TestCustomPaths.WithWorkingDirectory
TEST_PATHS
math
choice.py
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/test_a
)
set(EXPECTED
"TestCustomPaths.WithWorkingDirectory.test_addition"
"TestCustomPaths.WithWorkingDirectory.test_random"
)
add_test(NAME TestCustomPaths.Validate.WithWorkingDirectory
COMMAND ${CMAKE_COMMAND}
-D "TEST_PREFIX=TestCustomPaths.WithWorkingDirectory"
-D "EXPECTED=${EXPECTED}"
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
)
4 changes: 4 additions & 0 deletions test/08-test-paths/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[pytest]
testpaths =
test_a
test_b
4 changes: 4 additions & 0 deletions test/08-test-paths/test_a/choice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import random

def test_random():
assert random.choice([1, 2, 3]) in (1, 2, 3)
2 changes: 2 additions & 0 deletions test/08-test-paths/test_a/math/test_add.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test_addition():
assert 1 + 1 == 2
2 changes: 2 additions & 0 deletions test/08-test-paths/test_a/test_upper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test_upper():
assert "hello".upper() == "HELLO"
2 changes: 2 additions & 0 deletions test/08-test-paths/test_b/math/test_power.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test_power():
assert 2 ** 3 == 8
2 changes: 2 additions & 0 deletions test/08-test-paths/test_b/math/test_subtract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test_substraction():
assert 2 - 1 == 1
2 changes: 2 additions & 0 deletions test/08-test-paths/test_b/test_concat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test_concat():
assert "foo" + "bar" == "foobar"
1 change: 1 addition & 0 deletions test/08-test-paths/test_incorrect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
raise RuntimeError("This test files should have been excluded")
Loading
Loading