Skip to content

Commit 567b7a8

Browse files
committed
Add TEST_PATHS option and improve pytest discovery error detection
- Added `TEST_PATHS` to `pytest_discover_tests` to limit discovery to specific files or directories, defaulting to `testpaths` from pytest.ini if unset. - Updated error detection to catch both block-form and single-line messages from pytest. - Add tests.
1 parent 0a1f238 commit 567b7a8

File tree

19 files changed

+253
-26
lines changed

19 files changed

+253
-26
lines changed

cmake/FindPytest.cmake

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,34 @@ if (Pytest_FOUND AND NOT TARGET Pytest::Pytest)
5555

5656
# Function to discover pytest tests and add them to CTest.
5757
function(pytest_discover_tests NAME)
58+
set(_BOOL_ARGS
59+
STRIP_PARAM_BRACKETS
60+
INCLUDE_FILE_PATH
61+
BUNDLE_TESTS
62+
)
63+
64+
set(_SINGLE_VALUE_ARGS
65+
WORKING_DIRECTORY
66+
TRIM_FROM_NAME
67+
TRIM_FROM_FULL_NAME
68+
)
69+
70+
set(_MULTI_VALUE_ARGS
71+
TEST_PATHS
72+
LIBRARY_PATH_PREPEND
73+
PYTHON_PATH_PREPEND
74+
ENVIRONMENT
75+
PROPERTIES
76+
DEPENDS
77+
EXTRA_ARGS
78+
DISCOVERY_EXTRA_ARGS
79+
)
80+
5881
cmake_parse_arguments(
59-
PARSE_ARGV 1 "" "STRIP_PARAM_BRACKETS;INCLUDE_FILE_PATH;BUNDLE_TESTS"
60-
"WORKING_DIRECTORY;TRIM_FROM_NAME;TRIM_FROM_FULL_NAME"
61-
"LIBRARY_PATH_PREPEND;PYTHON_PATH_PREPEND;ENVIRONMENT;PROPERTIES;DEPENDS;EXTRA_ARGS;DISCOVERY_EXTRA_ARGS"
82+
PARSE_ARGV 1 ""
83+
"${_BOOL_ARGS}"
84+
"${_SINGLE_VALUE_ARGS}"
85+
"${_MULTI_VALUE_ARGS}"
6286
)
6387

6488
# Set platform-specific library path environment variable.
@@ -119,6 +143,7 @@ if (Pytest_FOUND AND NOT TARGET Pytest::Pytest)
119143
DEPENDS ${_DEPENDS}
120144
COMMAND ${CMAKE_COMMAND}
121145
-D "PYTEST_EXECUTABLE=${PYTEST_EXECUTABLE}"
146+
-D "TEST_PATHS=${_TEST_PATHS}"
122147
-D "TEST_GROUP_NAME=${NAME}"
123148
-D "BUNDLE_TESTS=${_BUNDLE_TESTS}"
124149
-D "LIBRARY_ENV_NAME=${LIBRARY_ENV_NAME}"
@@ -139,13 +164,13 @@ if (Pytest_FOUND AND NOT TARGET Pytest::Pytest)
139164
# Create a custom target to run the tests.
140165
add_custom_target(${NAME} ALL DEPENDS ${_tests_file})
141166

142-
file(WRITE "${_include_file}"
143-
"if(EXISTS \"${_tests_file}\")\n"
144-
" include(\"${_tests_file}\")\n"
145-
"else()\n"
146-
" add_test(${NAME}_NOT_BUILT ${NAME}_NOT_BUILT)\n"
147-
"endif()\n"
148-
)
167+
file(WRITE "${_include_file}"
168+
"if(EXISTS \"${_tests_file}\")\n"
169+
" include(\"${_tests_file}\")\n"
170+
"else()\n"
171+
" add_test(${NAME}_NOT_BUILT ${NAME}_NOT_BUILT)\n"
172+
"endif()\n"
173+
)
149174

150175
# Register the include file to be processed for tests.
151176
set_property(DIRECTORY

cmake/PytestAddTests.cmake

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,21 @@ if(CMAKE_SCRIPT_MODE_FILE)
3838
list(JOIN EXTRA_ARGS_WRAPPED " " EXTRA_ARGS_STR)
3939

4040
# Macro to create individual tests with optional test properties.
41-
macro(create_test NAME IDENTIFIER)
42-
string(APPEND _content
43-
"add_test([==[${NAME}]==] \"${PYTEST_EXECUTABLE}\" [==[${IDENTIFIER}]==] ${EXTRA_ARGS_STR} )\n"
44-
)
41+
macro(create_test NAME IDENTIFIERS)
42+
string(APPEND _content "add_test([==[${NAME}]==] \"${PYTEST_EXECUTABLE}\"")
43+
44+
foreach(identifier ${IDENTIFIERS})
45+
string(APPEND _content " [==[${identifier}]==]")
46+
endforeach()
47+
48+
string(APPEND _content " ${EXTRA_ARGS_STR} )\n")
4549

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

53+
# Add working directory
54+
string(APPEND args " WORKING_DIRECTORY [==[${WORKING_DIRECTORY}]==]")
55+
4956
# Append any additional properties, escaping complex characters if necessary.
5057
foreach(property ${TEST_PROPERTIES})
5158
if(property MATCHES "[^-./:a-zA-Z0-9_]")
@@ -61,27 +68,35 @@ if(CMAKE_SCRIPT_MODE_FILE)
6168

6269
# If tests are bundled together, create a single test group.
6370
if (BUNDLE_TESTS)
64-
create_test("\${TEST_GROUP_NAME}" "\${WORKING_DIRECTORY}")
71+
create_test("\${TEST_GROUP_NAME}" "\${TEST_PATHS}")
6572

6673
else()
6774
# Set environment variables for collecting tests.
6875
set(ENV{${LIBRARY_ENV_NAME}} "${LIBRARY_PATH}")
6976
set(ENV{PYTHONPATH} "${PYTHON_PATH}")
7077
set(ENV{PYTHONWARNINGS} "ignore")
7178

79+
set(_command
80+
"${PYTEST_EXECUTABLE}" --collect-only -q
81+
"--rootdir=${WORKING_DIRECTORY}"
82+
${DISCOVERY_EXTRA_ARGS}
83+
)
84+
85+
foreach(test_path IN LISTS TEST_PATHS)
86+
list(APPEND _command "${test_path}")
87+
endforeach()
88+
7289
# Collect tests.
7390
execute_process(
74-
COMMAND "${PYTEST_EXECUTABLE}"
75-
--collect-only -q
76-
--rootdir=${WORKING_DIRECTORY} ${DISCOVERY_EXTRA_ARGS} .
91+
COMMAND ${_command}
7792
OUTPUT_VARIABLE _output_lines
7893
ERROR_VARIABLE _output_lines
7994
OUTPUT_STRIP_TRAILING_WHITESPACE
8095
WORKING_DIRECTORY ${WORKING_DIRECTORY}
8196
)
8297

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

86101
if (_error)
87102
message(${_error})
@@ -141,7 +156,7 @@ if(CMAKE_SCRIPT_MODE_FILE)
141156

142157
# Prefix the test name with the test group name.
143158
set(test_name "${TEST_GROUP_NAME}.${test_name}")
144-
set(test_case "${WORKING_DIRECTORY}/${line}")
159+
set(test_case "${line}")
145160

146161
# Create the test for CTest.
147162
create_test("\${test_name}" "\${test_case}")

doc/api_reference.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ API Reference
1212
with :term:`Pytest` within a controlled environment::
1313

1414
pytest_discover_tests(NAME
15+
[TEST_PATHS path1 path2...]
1516
[WORKING_DIRECTORY dir]
1617
[TRIM_FROM_NAME pattern]
1718
[TRIM_FROM_FULL_NAME pattern]
@@ -35,6 +36,19 @@ API Reference
3536
used as a prefix for each test created, or as an identifier the bundled
3637
test.
3738

39+
* ``TEST_PATHS``
40+
41+
Specifies a list of files or directories to search when executing
42+
:term:`Pytest` from the current source directory (or from the
43+
``WORKING_DIRECTORY`` value if provided)::
44+
45+
pytest_discover_tests(
46+
...
47+
TEST_PATHS
48+
path1
49+
path2/test.py
50+
)
51+
3852
* ``WORKING_DIRECTORY``
3953

4054
Specify the directory in which to run the :term:`Pytest` command. If

doc/release/release_notes.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,25 @@ Release Notes
1010

1111
Added compatibility with CMake 4.1.
1212

13+
.. change:: new
14+
15+
Added ``TEST_PATHS`` option to the :func:`pytest_discover_tests`
16+
function, allowing users to limit test discovery and execution to
17+
specific files or directories. If not specified, ``TEST_PATHS``
18+
defaults to the ``testpaths`` setting in :file:`pytest.ini`, or to the
19+
current directory if ``testpaths`` is not set. This matches
20+
:term:`Pytest`’s native behavior and preserves full backward
21+
compatibility.
22+
23+
.. seealso::
24+
25+
`'testpaths' configuration option
26+
<https://docs.pytest.org/en/stable/reference/reference.html#confval-testpaths>`_
27+
28+
.. change:: fixed
29+
30+
Updated discovery error detection to recognize both block-form and
31+
single-line messages from :term:`Pytest`.
1332

1433
.. release:: 0.13.0
1534
:date: 2025-02-16

test/01-modify-name/CMakeLists.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ add_test(NAME TestModifyName.Validate.Simple
2828
COMMAND ${CMAKE_COMMAND}
2929
-D "TEST_PREFIX=TestModifyName.Simple"
3030
-D "EXPECTED=${EXPECTED}"
31-
-P ${CMAKE_CURRENT_LIST_DIR}/compare_discovered_tests.cmake
31+
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
3232
)
3333

3434
pytest_discover_tests(TestModifyName.Bundled BUNDLE_TESTS)
3535
add_test(NAME TestModifyName.Validate.Bundled
3636
COMMAND ${CMAKE_COMMAND}
3737
-D "TEST_PREFIX=TestModifyName.Bundled"
3838
-D "EXPECTED=TestModifyName.Bundled"
39-
-P ${CMAKE_CURRENT_LIST_DIR}/compare_discovered_tests.cmake
39+
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
4040
)
4141

4242
pytest_discover_tests(
@@ -64,7 +64,7 @@ add_test(NAME TestModifyName.Validate.TrimFromName
6464
COMMAND ${CMAKE_COMMAND}
6565
-D "TEST_PREFIX=TestModifyName.TrimFromName"
6666
-D "EXPECTED=${EXPECTED}"
67-
-P ${CMAKE_CURRENT_LIST_DIR}/compare_discovered_tests.cmake
67+
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
6868
)
6969

7070
pytest_discover_tests(
@@ -92,7 +92,7 @@ add_test(NAME TestModifyName.Validate.StripParamBrackets
9292
COMMAND ${CMAKE_COMMAND}
9393
-D "TEST_PREFIX=TestModifyName.StripParamBrackets"
9494
-D "EXPECTED=${EXPECTED}"
95-
-P ${CMAKE_CURRENT_LIST_DIR}/compare_discovered_tests.cmake
95+
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
9696
)
9797

9898
pytest_discover_tests(
@@ -120,7 +120,7 @@ add_test(NAME TestModifyName.Validate.IncludeFilePath
120120
COMMAND ${CMAKE_COMMAND}
121121
-D "TEST_PREFIX=TestModifyName.IncludeFilePath"
122122
-D "EXPECTED=${EXPECTED}"
123-
-P ${CMAKE_CURRENT_LIST_DIR}/compare_discovered_tests.cmake
123+
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
124124
)
125125

126126
pytest_discover_tests(
@@ -149,5 +149,5 @@ add_test(NAME TestModifyName.Validate.TrimFromFullName
149149
COMMAND ${CMAKE_COMMAND}
150150
-D "TEST_PREFIX=TestModifyName.TrimFromFullName"
151151
-D "EXPECTED=${EXPECTED}"
152-
-P ${CMAKE_CURRENT_LIST_DIR}/compare_discovered_tests.cmake
152+
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
153153
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
cmake_minimum_required(VERSION 3.20)
2+
3+
project(TestWorkingDirectory)
4+
5+
find_package(Pytest REQUIRED)
6+
7+
enable_testing()
8+
9+
pytest_discover_tests(
10+
TestWorkingDirectory
11+
WORKING_DIRECTORY subdir
12+
)
13+
14+
pytest_discover_tests(
15+
TestWorkingDirectory.Bundled
16+
WORKING_DIRECTORY subdir
17+
BUNDLE_TESTS
18+
)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def test_addition():
2+
assert 1 + 1 == 2
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
raise RuntimeError("This test files should have been excluded")

test/08-test-paths/CMakeLists.txt

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
cmake_minimum_required(VERSION 3.20)
2+
3+
project(TestCustomPaths)
4+
5+
find_package(Pytest REQUIRED)
6+
7+
enable_testing()
8+
9+
pytest_discover_tests(
10+
TestCustomPaths.FromConfig
11+
)
12+
set(EXPECTED
13+
"TestCustomPaths.FromConfig.test_addition"
14+
"TestCustomPaths.FromConfig.test_concat"
15+
"TestCustomPaths.FromConfig.test_power"
16+
"TestCustomPaths.FromConfig.test_substraction"
17+
"TestCustomPaths.FromConfig.test_upper"
18+
)
19+
add_test(NAME TestCustomPaths.Validate.FromConfig
20+
COMMAND ${CMAKE_COMMAND}
21+
-D "TEST_PREFIX=TestCustomPaths.FromConfig"
22+
-D "EXPECTED=${EXPECTED}"
23+
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
24+
)
25+
26+
pytest_discover_tests(
27+
TestCustomPaths.BundledFromConfig
28+
BUNDLE_TESTS
29+
)
30+
set(EXPECTED
31+
"TestCustomPaths.BundledFromConfig"
32+
)
33+
add_test(NAME TestCustomPaths.Validate.BundledFromConfig
34+
COMMAND ${CMAKE_COMMAND}
35+
-D "TEST_PREFIX=TestCustomPaths.BundledFromConfig"
36+
-D "EXPECTED=${EXPECTED}"
37+
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
38+
)
39+
40+
pytest_discover_tests(
41+
TestCustomPaths.FilePaths
42+
TEST_PATHS
43+
${CMAKE_CURRENT_LIST_DIR}/test_a/math/test_add.py
44+
test_a/choice.py
45+
test_b/test_concat.py
46+
)
47+
set(EXPECTED
48+
"TestCustomPaths.FilePaths.test_addition"
49+
"TestCustomPaths.FilePaths.test_concat"
50+
"TestCustomPaths.FilePaths.test_random"
51+
)
52+
add_test(NAME TestCustomPaths.Validate.FilePaths
53+
COMMAND ${CMAKE_COMMAND}
54+
-D "TEST_PREFIX=TestCustomPaths.FilePaths"
55+
-D "EXPECTED=${EXPECTED}"
56+
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
57+
)
58+
59+
pytest_discover_tests(
60+
TestCustomPaths.DirPaths
61+
TEST_PATHS
62+
test_a
63+
${CMAKE_CURRENT_LIST_DIR}/test_b/math
64+
)
65+
set(EXPECTED
66+
"TestCustomPaths.DirPaths.test_addition"
67+
"TestCustomPaths.DirPaths.test_power"
68+
"TestCustomPaths.DirPaths.test_substraction"
69+
"TestCustomPaths.DirPaths.test_upper"
70+
)
71+
add_test(NAME TestCustomPaths.Validate.DirPaths
72+
COMMAND ${CMAKE_COMMAND}
73+
-D "TEST_PREFIX=TestCustomPaths.DirPaths"
74+
-D "EXPECTED=${EXPECTED}"
75+
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
76+
)
77+
78+
pytest_discover_tests(
79+
TestCustomPaths.WithWorkingDirectory
80+
TEST_PATHS
81+
math
82+
choice.py
83+
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/test_a
84+
)
85+
set(EXPECTED
86+
"TestCustomPaths.WithWorkingDirectory.test_addition"
87+
"TestCustomPaths.WithWorkingDirectory.test_random"
88+
)
89+
add_test(NAME TestCustomPaths.Validate.WithWorkingDirectory
90+
COMMAND ${CMAKE_COMMAND}
91+
-D "TEST_PREFIX=TestCustomPaths.WithWorkingDirectory"
92+
-D "EXPECTED=${EXPECTED}"
93+
-P ${CMAKE_CURRENT_LIST_DIR}/../utils/compare_discovered_tests.cmake
94+
)

test/08-test-paths/pytest.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[pytest]
2+
testpaths =
3+
test_a
4+
test_b

0 commit comments

Comments
 (0)