Skip to content

Commit 130938a

Browse files
Allow CTest as a test driver (#870)
* Allow fine-grained test control via CTest Previously, a single test was defined for test-libmongoc. This change allows CTest to see the individual test cases contained within test-libmongoc. These can be selected and executed with the CTest CLI, including with some parallelism. Tag CXX-2301 CXX-2302 * Fix duplicate test registration * Use a signal handler to see abnormal termination. * Modify multi-conf test behavior to match VS behavior
1 parent 766900b commit 130938a

File tree

7 files changed

+210
-35
lines changed

7 files changed

+210
-35
lines changed

CMakeLists.txt

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ else ()
6363
message (STATUS "No CXX support")
6464
endif ()
6565

66-
if (NOT CMAKE_BUILD_TYPE)
66+
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
6767
set (CMAKE_BUILD_TYPE "RelWithDebInfo")
6868
message (
6969
STATUS "No CMAKE_BUILD_TYPE selected, defaulting to ${CMAKE_BUILD_TYPE}"
@@ -211,6 +211,9 @@ set (BUILD_SOURCE_DIR ${CMAKE_BINARY_DIR})
211211

212212
include (MakeDistFiles)
213213

214+
# Enable CTest
215+
include (CTest)
216+
214217
# Ensure the default behavior: don't ignore RPATH settings.
215218
set (CMAKE_SKIP_BUILD_RPATH OFF)
216219

@@ -491,3 +494,39 @@ if (CMAKE_GENERATOR_TOOLSET)
491494
message (STATUS "\tinstance: ${CMAKE_GENERATOR_TOOLSET}")
492495
endif ()
493496

497+
if (TARGET test-libmongoc)
498+
# Generate a file that can be included by CTest to load and enumerate all of the
499+
# tests defined by the test-libmongoc executable. Generate one for each
500+
# configuration in case of multiconf generators.
501+
string (CONFIGURE [=[
502+
set (TEST_LIBMONGOC_EXE [[$<TARGET_FILE:test-libmongoc>]])
503+
set (SRC_ROOT [[@PROJECT_SOURCE_DIR@]])
504+
set (IS_MULTICONF $<BOOL:@CMAKE_CONFIGURATION_TYPES@>)
505+
if (NOT IS_MULTICONF OR CTEST_CONFIGURATION_TYPE STREQUAL "$<CONFIG>")
506+
# We are not in multi-conf, or the current config matches our config.
507+
include ("${SRC_ROOT}/build/cmake/LoadTests.cmake")
508+
elseif (NOT CTEST_CONFIGURATION_TYPE)
509+
# We are in multi-conf, but no '-C' config was specified
510+
message (WARNING "Specify a --build-config when using CTest with a multi-config build")
511+
else ()
512+
# Do nothing. Not our config.
513+
endif ()
514+
]=] code @ONLY)
515+
file (GENERATE
516+
OUTPUT "${PROJECT_BINARY_DIR}/LoadTests-$<CONFIG>.cmake"
517+
CONTENT "${code}")
518+
if (CMAKE_CONFIGURATION_TYPES)
519+
foreach (conf IN LISTS CMAKE_CONFIGURATION_TYPES)
520+
# Direct the generated CTest code to include() the file that loads the tests:
521+
set_property (
522+
DIRECTORY
523+
APPEND PROPERTY
524+
TEST_INCLUDE_FILES "${PROJECT_BINARY_DIR}/LoadTests-${conf}.cmake")
525+
endforeach ()
526+
else ()
527+
set_property (
528+
DIRECTORY
529+
APPEND PROPERTY
530+
TEST_INCLUDE_FILES "${PROJECT_BINARY_DIR}/LoadTests-${CMAKE_BUILD_TYPE}.cmake")
531+
endif ()
532+
endif ()

build/cmake/LoadTests.cmake

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# This file is include()'d by CTest. It executes test-libmongoc to get a list
2+
# of all tests that are registered. Each test is then defined as a CTest test,
3+
# allowing CTest to control the execution, parallelization, and collection of
4+
# test results.
5+
6+
if (NOT EXISTS "${TEST_LIBMONGOC_EXE}")
7+
# This will fail if 'test-libmongoc' is not compiled yet.
8+
message (WARNING "The test executable ${TEST_LIBMONGOC_EXE} is not present. "
9+
"Its tests will not be registered")
10+
add_test (mongoc/not-found NOT_FOUND)
11+
return ()
12+
endif ()
13+
14+
# Get the list of tests
15+
execute_process (
16+
COMMAND "${TEST_LIBMONGOC_EXE}" --list-tests --no-fork
17+
OUTPUT_VARIABLE tests_out
18+
WORKING_DIRECTORY "${SRC_ROOT}"
19+
RESULT_VARIABLE retc
20+
)
21+
if (retc)
22+
# Failed to list the tests. That's bad.
23+
message (FATAL_ERROR "Failed to run test-libmongoc to discover tests [${retc}]:\n${tests_out}")
24+
endif ()
25+
26+
# Split lines on newlines
27+
string (REPLACE "\n" ";" lines "${tests_out}")
28+
29+
# Generate the test definitions
30+
foreach (line IN LISTS lines)
31+
if (NOT line MATCHES "^/")
32+
# Only generate if the line begins with `/`, which all tests should.
33+
continue ()
34+
endif ()
35+
# The new test name is prefixed with 'mongoc'
36+
set (test "mongoc${line}")
37+
# Define the test. Use `--ctest-run` to tell it that CTest is in control.
38+
add_test ("${test}" "${TEST_LIBMONGOC_EXE}" --ctest-run "${line}")
39+
set_tests_properties ("${test}" PROPERTIES
40+
# test-libmongoc expects to execute in the root of the source directory
41+
WORKING_DIRECTORY "${SRC_ROOT}"
42+
# If a test emits '@@ctest-skipped@@', this tells us that the test is
43+
# skipped.
44+
SKIP_REGULAR_EXPRESSION "@@ctest-skipped@@"
45+
# 45 seconds of timeout on each test.
46+
TIMEOUT 45
47+
)
48+
endforeach ()

src/libbson/src/bson/bson-macros.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,11 @@
306306
#define BSON_GNUC_DEPRECATED_FOR(f) BSON_GNUC_DEPRECATED
307307
#endif
308308

309+
/**
310+
* @brief String-ify the given argument
311+
*/
312+
#define BSON_STR(...) #__VA_ARGS__
313+
309314
/**
310315
* @brief Mark the attached declared entity as "possibly-unused."
311316
*

src/libmongoc/CMakeLists.txt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,12 +1042,6 @@ mongoc_add_test (test-mongoc-gssapi FALSE ${PROJECT_SOURCE_DIR}/tests/test-mongo
10421042
mongoc_add_test (test-mongoc-cache FALSE ${PROJECT_SOURCE_DIR}/tests/test-mongoc-cache.c)
10431043

10441044
if (ENABLE_TESTS)
1045-
enable_testing ()
1046-
add_test (NAME test-libmongoc
1047-
COMMAND test-libmongoc
1048-
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/../..
1049-
)
1050-
10511045
# "make test" doesn't compile tests, so we create "make check" which compiles
10521046
# and runs tests: https://gitlab.kitware.com/cmake/cmake/issues/8774
10531047
add_custom_target (check COMMAND ${CMAKE_CTEST_COMMAND} -V

src/libmongoc/tests/TestSuite.c

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <stdio.h>
2626
#include <stdlib.h>
2727
#include <string.h>
28+
#include <signal.h>
2829
#include <mongoc/mongoc-util-private.h>
2930
#if !defined(_WIN32)
3031
#include <sys/types.h>
@@ -112,6 +113,43 @@ static BSON_ONCE_FUN (_test_suite_ensure_mutex_once)
112113
}
113114

114115

116+
static void
117+
_handle_signal (int signum)
118+
{
119+
const char *s = "\nProcess was interrupted by the delivery of a signal.\n";
120+
const char *sigstr;
121+
switch (signum) {
122+
case SIGABRT:
123+
sigstr = "SIGABRT - Abnormal termination";
124+
break;
125+
case SIGINT:
126+
sigstr = "SIGINT - Interrupted";
127+
break;
128+
case SIGTERM:
129+
sigstr = "SIGTERM - Termination requested";
130+
break;
131+
case SIGSEGV:
132+
sigstr = "SIGSEGV - Access violation";
133+
break;
134+
default:
135+
sigstr = "(Unknown signal delivered)";
136+
}
137+
#ifdef BSON_OS_UNIX
138+
/* On POSIX these APIs are signal-safe */
139+
write (STDERR_FILENO, s, strlen (s));
140+
write (STDERR_FILENO, " ", 2);
141+
write (STDERR_FILENO, sigstr, strlen (sigstr));
142+
write (STDERR_FILENO, "\n", 1);
143+
fsync (STDERR_FILENO);
144+
#else
145+
/* On Windows these APIs are signal-safe */
146+
fprintf (stderr, "\n%s\n %s\n", s, sigstr);
147+
fflush (stderr);
148+
#endif
149+
_Exit (signum);
150+
}
151+
152+
115153
void
116154
TestSuite_Init (TestSuite *suite, const char *name, int argc, char **argv)
117155
{
@@ -125,8 +163,14 @@ TestSuite_Init (TestSuite *suite, const char *name, int argc, char **argv)
125163
suite->flags = 0;
126164
suite->prgname = bson_strdup (argv[0]);
127165
suite->silent = false;
166+
suite->ctest_run = NULL;
128167
_mongoc_array_init (&suite->match_patterns, sizeof (char *));
129168

169+
suite->prev_sigabrt = signal (SIGABRT, _handle_signal);
170+
suite->prev_sigint = signal (SIGINT, _handle_signal);
171+
suite->prev_sigterm = signal (SIGTERM, _handle_signal);
172+
suite->prev_sigsegv = signal (SIGSEGV, _handle_signal);
173+
130174
for (i = 1; i < argc; i++) {
131175
if (0 == strcmp ("-d", argv[i])) {
132176
suite->flags |= TEST_DEBUGOUTPUT;
@@ -165,6 +209,16 @@ TestSuite_Init (TestSuite *suite, const char *name, int argc, char **argv)
165209
} else if ((0 == strcmp ("-s", argv[i])) ||
166210
(0 == strcmp ("--silent", argv[i]))) {
167211
suite->silent = true;
212+
} else if ((0 == strcmp ("--ctest-run", argv[i]))) {
213+
if (suite->ctest_run) {
214+
test_error ("'--ctest-run' can only be specified once");
215+
}
216+
if (argc - 1 == i) {
217+
test_error ("'--ctest-run' requires an argument");
218+
}
219+
suite->flags |= TEST_NOFORK;
220+
suite->silent = true;
221+
suite->ctest_run = bson_strdup (argv[++i]);
168222
} else if ((0 == strcmp ("-l", argv[i])) ||
169223
(0 == strcmp ("--match", argv[i]))) {
170224
char *val;
@@ -180,6 +234,10 @@ TestSuite_Init (TestSuite *suite, const char *name, int argc, char **argv)
180234
}
181235
}
182236

237+
if (suite->match_patterns.len != 0 && suite->ctest_run != NULL) {
238+
test_error ("'--ctest-run' cannot be specified with '-l' or '--match'");
239+
}
240+
183241
if (test_framework_getenv_bool ("MONGOC_TEST_VALGRIND")) {
184242
suite->flags |= TEST_VALGRIND;
185243
}
@@ -559,6 +617,10 @@ TestSuite_RunTest (TestSuite *suite, /* IN */
559617

560618
for (i = 0; i < test->num_checks; i++) {
561619
if (!test->checks[i]()) {
620+
if (suite->ctest_run) {
621+
/* Write a marker that tells CTest that we are skipping this test */
622+
test_msg ("@@ctest-skipped@@");
623+
}
562624
if (!suite->silent) {
563625
bson_string_append_printf (
564626
buf,
@@ -667,6 +729,8 @@ TestSuite_PrintHelp (TestSuite *suite) /* IN */
667729
"first error).\n"
668730
" -l, --match PATTERN Run test by name, e.g. \"/Client/command\" or "
669731
"\"/Client/*\". May be repeated.\n"
732+
" --ctest-run TEST Run only the named TEST for CTest\n"
733+
" integration.\n"
670734
" -s, --silent Suppress all output.\n"
671735
" -F FILENAME Write test results (JSON) to FILENAME.\n"
672736
" -d Print debug output (useful if a test hangs).\n"
@@ -878,6 +942,11 @@ test_matches (TestSuite *suite, Test *test)
878942
{
879943
int i;
880944

945+
if (suite->ctest_run) {
946+
/* We only want exactly the named test */
947+
return strcmp (test->name, suite->ctest_run) == 0;
948+
}
949+
881950
/* If no match patterns were provided, then assume all match. */
882951
if (suite->match_patterns.len == 0) {
883952
return true;
@@ -909,6 +978,14 @@ TestSuite_RunAll (TestSuite *suite /* IN */)
909978
}
910979
}
911980

981+
if (suite->ctest_run) {
982+
/* We should have matched *at most* one test */
983+
ASSERT (count <= 1);
984+
if (count == 0) {
985+
test_error ("No such test '%s'", suite->ctest_run);
986+
}
987+
}
988+
912989
for (test = suite->tests; test; test = test->next) {
913990
if (test_matches (suite, test)) {
914991
status += TestSuite_RunTest (suite, test, &count);
@@ -1001,12 +1078,18 @@ TestSuite_Destroy (TestSuite *suite)
10011078

10021079
free (suite->name);
10031080
free (suite->prgname);
1081+
free (suite->ctest_run);
10041082
for (i = 0; i < suite->match_patterns.len; i++) {
10051083
char *val = _mongoc_array_index (&suite->match_patterns, char *, i);
10061084
bson_free (val);
10071085
}
10081086

10091087
_mongoc_array_destroy (&suite->match_patterns);
1088+
1089+
signal (SIGABRT, suite->prev_sigabrt);
1090+
signal (SIGINT, suite->prev_sigint);
1091+
signal (SIGTERM, suite->prev_sigterm);
1092+
signal (SIGSEGV, suite->prev_sigsegv);
10101093
}
10111094

10121095

0 commit comments

Comments
 (0)