Skip to content

Commit 9ec44dd

Browse files
committed
catch_discover_tests uses tempfile to retrieve JSON from the binary
This allows it to deal with badly behaved code, where 3rd party dependencies write into stdout during global construction. Closes #3162 Closes #3166
1 parent 675f9ea commit 9ec44dd

4 files changed

Lines changed: 92 additions & 15 deletions

File tree

extras/Catch.cmake

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
4444
4545
``catch_discover_tests`` sets up a post-build command on the test executable
4646
that generates the list of tests by parsing the output from running the test
47-
with the ``--list-test-names-only`` argument. This ensures that the full
47+
with the ``--list-tests --reporter json`` argument. This ensures that the full
4848
list of tests is obtained. Since test discovery occurs at build time, it is
4949
not necessary to re-run CMake when the list of tests changes.
5050
However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
@@ -67,7 +67,7 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
6767
6868
``TEST_SPEC arg1...``
6969
Specifies test cases, wildcarded test cases, tags and tag expressions to
70-
pass to the Catch executable with the ``--list-test-names-only`` argument.
70+
pass to the Catch executable when listing the tests.
7171
7272
``EXTRA_ARGS arg1...``
7373
Any extra arguments to pass on the command line to each test case.

extras/CatchAddTests.cmake

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,42 @@ function(add_command NAME)
1616
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
1717
endfunction()
1818

19+
# Generates random filename in the temp folder.
20+
# Temp folder is retrieved by checking env vars from various platforms.
21+
function(make_temp_file_path OUT_VARIABLE FALLBACK_PATH)
22+
set(TEMP_DIR "")
23+
set(ENV_VARS
24+
# From XDG base dir specification
25+
XDG_RUNTIME_DIR
26+
# From POSIX standard
27+
TMPDIR
28+
# From Windows
29+
TMP
30+
TEMP
31+
)
32+
33+
foreach(var ${ENV_VARS})
34+
message(NOTICE "Checking ${var} env var")
35+
if(DEFINED ENV{${var}} AND NOT "$ENV{${var}}" STREQUAL "")
36+
set(TEMP_DIR "$ENV{${var}}")
37+
break()
38+
endif()
39+
endforeach()
40+
41+
# If all checks fail, we use the fallback path
42+
if(TEMP_DIR STREQUAL "")
43+
set(TEMP_DIR "${FALLBACK_PATH}")
44+
endif()
45+
46+
file(TO_CMAKE_PATH "${TEMP_DIR}" TEMP_DIR)
47+
48+
# Generate the random file name
49+
string(RANDOM LENGTH 8 RAND_ID)
50+
set(FINAL_TEMP_PATH "${TEMP_DIR}/Catch2-test-listing.${RAND_ID}.json")
51+
52+
set(${OUT_VARIABLE} "${FINAL_TEMP_PATH}" PARENT_SCOPE)
53+
endfunction()
54+
1955
function(catch_discover_tests_impl)
2056

2157
cmake_parse_arguments(
@@ -72,8 +108,13 @@ function(catch_discover_tests_impl)
72108
set(ENV{DYLD_FRAMEWORK_PATH} "${paths}")
73109
endif()
74110

111+
make_temp_file_path(listing_output_path "${_TEST_WORKING_DIR}")
112+
75113
execute_process(
76-
COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" ${spec} --list-tests --reporter json
114+
COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" ${spec}
115+
--list-tests
116+
--reporter json
117+
--out "${listing_output_path}"
77118
OUTPUT_VARIABLE listing_output
78119
RESULT_VARIABLE result
79120
WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
@@ -86,6 +127,10 @@ function(catch_discover_tests_impl)
86127
)
87128
endif()
88129

130+
# Read the JSON output back from the output file (and then get rid of the file)
131+
file(READ ${listing_output_path} listing_output)
132+
file(REMOVE ${listing_output_path})
133+
89134
# Prepare reporter
90135
if(reporter)
91136
set(reporter_arg "--reporter ${reporter}")

tests/TestScripts/DiscoverTests/VerifyRegistration.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import sys
1313
import re
1414
import json
15+
import tempfile
1516
from collections import namedtuple
1617
from typing import List
1718

@@ -72,14 +73,19 @@ def get_test_names(build_path: str) -> List[TestInfo]:
7273
config_path = "Debug" if os.name == 'nt' else ""
7374
full_path = os.path.join(build_path, config_path, 'tests')
7475

75-
76-
cmd = [full_path, '--reporter', 'json', '--list-tests']
77-
result = subprocess.run(cmd,
78-
capture_output = True,
79-
check = True,
80-
text = True)
81-
82-
test_listing = json.loads(result.stdout)
76+
with tempfile.TemporaryDirectory() as tmpdir:
77+
fname = f'{tmpdir}/listing-output.json'
78+
cmd = [full_path,
79+
'--list-tests',
80+
'--reporter', 'json',
81+
'--out', fname
82+
]
83+
result = subprocess.run(cmd,
84+
capture_output = False,
85+
check = True,
86+
text = True)
87+
with open(fname, mode='r', encoding='utf-8') as file:
88+
test_listing = json.load(file)
8389

8490
assert test_listing['version'] == 1
8591

@@ -96,10 +102,18 @@ def get_ctest_listing(build_path):
96102
os.chdir(build_path)
97103

98104
cmd = ['ctest', '-C', 'debug', '--show-only=json-v1']
99-
result = subprocess.run(cmd,
100-
capture_output = True,
101-
check = True,
102-
text = True)
105+
try:
106+
result = subprocess.run(cmd,
107+
capture_output = True,
108+
check = True,
109+
text = True)
110+
except subprocess.CalledProcessError as err:
111+
print('Error when getting output from CTest')
112+
print(f'cmd: {err.cmd}')
113+
print(f'stderr: {err.stderr}')
114+
print(f'stdout: {err.stdout}')
115+
exit(4)
116+
103117
os.chdir(old_path)
104118
return result.stdout
105119

tests/TestScripts/DiscoverTests/register-tests.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,24 @@
88

99
#include <catch2/catch_test_macros.hpp>
1010

11+
#include <cstdio>
12+
#include <iostream>
13+
14+
namespace {
15+
16+
struct PrintsWhenConstructed {
17+
PrintsWhenConstructed() {
18+
std::cout << "Hello\n";
19+
std::cerr << "Holla\n";
20+
std::fprintf(stdout, "Hullo\n");
21+
std::fprintf(stderr, "Hillo\n");
22+
}
23+
};
24+
25+
static PrintsWhenConstructed instance;
26+
27+
}
28+
1129
TEST_CASE("@Script[C:\\EPM1A]=x;\"SCALA_ZERO:\"", "[script regressions]"){}
1230
TEST_CASE("Some test") {}
1331
TEST_CASE( "Let's have a test case with a long name. Longer. No, even longer. "

0 commit comments

Comments
 (0)