Skip to content

Commit b39b795

Browse files
Add support for test cases to declare tags (labels)
This change allows for test cases to declare any number of associated "tags". The tags are specified after the test case name string as a list of bracketed tags, mimicking Catch2's syntax. The LoadTests.cmake script has been modified to apply test case's declared tags as CTest labels. This also gives us the ability to apply test fixture requirements granularly on only tests that declare their requirement (via a tag).
1 parent 47629d7 commit b39b795

File tree

4 files changed

+117
-58
lines changed

4 files changed

+117
-58
lines changed

build/cmake/LoadTests.cmake

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,68 @@
33
# allowing CTest to control the execution, parallelization, and collection of
44
# test results.
55

6-
if (NOT EXISTS "${TEST_LIBMONGOC_EXE}")
6+
if(NOT EXISTS "${TEST_LIBMONGOC_EXE}")
77
# This will fail if 'test-libmongoc' is not compiled yet.
8-
message (WARNING "The test executable ${TEST_LIBMONGOC_EXE} is not present. "
8+
message(WARNING "The test executable ${TEST_LIBMONGOC_EXE} is not present. "
99
"Its tests will not be registered")
10-
add_test (mongoc/not-found NOT_FOUND)
11-
return ()
12-
endif ()
10+
add_test(mongoc/not-found NOT_FOUND)
11+
return()
12+
endif()
1313

14-
# Get the list of tests
15-
execute_process (
16-
COMMAND "${TEST_LIBMONGOC_EXE}" --list-tests --no-fork
17-
OUTPUT_VARIABLE tests_out
14+
# Get the list of tests. This command emits CMake code that defines variables for
15+
# all test cases defined in the suite
16+
execute_process(
17+
COMMAND "${TEST_LIBMONGOC_EXE}" --tests-cmake --no-fork
18+
OUTPUT_VARIABLE tests_cmake
1819
WORKING_DIRECTORY "${SRC_ROOT}"
1920
RESULT_VARIABLE retc
2021
)
21-
if (retc)
22+
if(retc)
2223
# 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 ()
24+
message(FATAL_ERROR "Failed to run test-libmongoc to discover tests [${retc}]:\n${tests_out}")
25+
endif()
2526

26-
# Split lines on newlines
27-
string (REPLACE "\n" ";" lines "${tests_out}")
27+
# Execute the code that defines the test case information
28+
cmake_language(EVAL CODE "${tests_cmake}")
2829

29-
# TODO: Allow individual test cases to specify the fixtures they want.
30-
set (all_fixtures "mongoc/fixtures/fake_kms_provider_server")
31-
set (all_env
30+
# Define environment variables that are common to all test cases
31+
set(all_env
3232
TEST_KMS_PROVIDER_HOST=localhost:14987 # Refer: Fixtures.cmake
3333
)
3434

35-
# Generate the test definitions
36-
foreach (line IN LISTS lines)
37-
if (NOT line MATCHES "^/")
38-
# Only generate if the line begins with `/`, which all tests should.
39-
continue ()
40-
endif ()
41-
# The new test name is prefixed with 'mongoc'
42-
set (test "mongoc${line}")
43-
# Define the test. Use `--ctest-run` to tell it that CTest is in control.
44-
add_test ("${test}" "${TEST_LIBMONGOC_EXE}" --ctest-run "${line}")
45-
set_tests_properties ("${test}" PROPERTIES
35+
# The emitted code defines a list MONGOC_TESTS with the name of every test case
36+
# in the suite.
37+
foreach(casename IN LISTS MONGOC_TESTS)
38+
set(name "mongoc${casename}")
39+
# Run the program with --ctest-run to select only this one test case
40+
add_test("${name}" "${TEST_LIBMONGOC_EXE}" --ctest-run "${casename}")
41+
# The emitted code defines a TAGS list for every test case that it emits. We use
42+
# these as the LABELS for the test case
43+
set(labels "${MONGOC_TEST_${casename}_TAGS}")
44+
45+
# Find what test fixtures the test wants by inspecting labels. Each "uses:"
46+
# label defines the names of the test fixtures that a particular case requires
47+
set(fixtures "${labels}")
48+
list(FILTER fixtures INCLUDE REGEX "^uses:")
49+
list(TRANSFORM fixtures REPLACE "^uses:(.*)$" "mongoc/fixtures/\\1")
50+
51+
# Add a label for all test cases generated via this script so that they
52+
# can be (de)selected separately:
53+
list(APPEND labels test-libmongoc-generated)
54+
# Set up the test:
55+
set_tests_properties("${name}" PROPERTIES
4656
# test-libmongoc expects to execute in the root of the source directory
4757
WORKING_DIRECTORY "${SRC_ROOT}"
4858
# If a test emits '@@ctest-skipped@@', this tells us that the test is
4959
# skipped.
5060
SKIP_REGULAR_EXPRESSION "@@ctest-skipped@@"
5161
# 45 seconds of timeout on each test.
5262
TIMEOUT 45
53-
FIXTURES_REQUIRED "${all_fixtures}"
63+
# Common environment variables:
5464
ENVIRONMENT "${all_env}"
55-
# Mark all tests generated from the executable, so they can be (de)selected
56-
# for execution separately.
57-
LABELS "test-libmongoc-generated"
58-
)
59-
endforeach ()
65+
# Apply the labels
66+
LABELS "${labels}"
67+
# Fixture requirements:
68+
FIXTURES_REQUIRED "${fixtures}"
69+
)
70+
endforeach()

src/libmongoc/tests/TestSuite.c

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <mongoc/mongoc.h>
2121

2222
#include <bson/bson.h>
23+
#include <bson/bson_t.h>
2324

2425
#include <mlib/str.h>
2526

@@ -168,6 +169,8 @@ TestSuite_Init(TestSuite *suite, const char *name, int argc, char **argv)
168169
suite->flags |= TEST_HELPTEXT;
169170
} else if (0 == strcmp("--list-tests", argv[i])) {
170171
suite->flags |= TEST_LISTTESTS;
172+
} else if (0 == strcmp("--tests-cmake", argv[i])) {
173+
suite->flags |= TEST_TESTS_CMAKE;
171174
} else if ((0 == strcmp("-s", argv[i])) || (0 == strcmp("--silent", argv[i]))) {
172175
suite->silent = true;
173176
} else if ((0 == strcmp("--ctest-run", argv[i]))) {
@@ -280,19 +283,37 @@ TestSuite_AddLive(TestSuite *suite, /* IN */
280283

281284

282285
Test *
283-
_V_TestSuite_AddFull(TestSuite *suite, const char *name, TestFuncWC func, TestFuncDtor dtor, void *ctx, va_list ap)
286+
_V_TestSuite_AddFull(
287+
TestSuite *suite, const char *name_and_tags, TestFuncWC func, TestFuncDtor dtor, void *ctx, va_list ap)
284288
{
285-
if (suite->ctest_run.data && mstr_cmp(suite->ctest_run, !=, mstr_cstring(name))) {
289+
// Split the name and tags around the first whitespace:
290+
mstr_view name, tags;
291+
mstr_split_around(mstr_cstring(name_and_tags), mstr_cstring(" "), &name, &tags);
292+
// Trim extras:
293+
tags = mstr_trim(tags);
294+
295+
if (suite->ctest_run.data && mstr_cmp(suite->ctest_run, !=, name)) {
296+
// We are running CTest, and not running this particular test, so just skip registering it
286297
if (dtor) {
287298
dtor(ctx);
288299
}
289300
return NULL;
290301
}
291302

292303
Test *test = TestVec_push(&suite->tests);
293-
test->name = mstr_copy_cstring(name);
304+
test->name = mstr_copy(name);
294305
test->func = func;
295306

307+
mstr_view tag, tail;
308+
tail = tags;
309+
while (tail.len) {
310+
mlib_check(mstr_split_around(tail, mstr_cstring("["), NULL, &tag),
311+
because,
312+
"Expected an opening bracket for the next test tag");
313+
mlib_check(mstr_split_around(tag, mstr_cstring("]"), &tag, &tail), because, "Expected a closing bracket for tag");
314+
*mstr_vec_push(&test->tags) = mstr_copy(tag);
315+
}
316+
296317
CheckFunc check;
297318
while ((check = va_arg(ap, CheckFunc))) {
298319
*CheckFuncVec_push(&test->checks) = check;
@@ -334,13 +355,12 @@ TestSuite_AddWC(TestSuite *suite, /* IN */
334355
}
335356

336357

337-
void
338-
_TestSuite_AddFull(TestSuite *suite, /* IN */
339-
const char *name, /* IN */
340-
TestFuncWC func, /* IN */
341-
TestFuncDtor dtor, /* IN */
342-
void *ctx,
343-
...) /* IN */
358+
void(TestSuite_AddFull)(TestSuite *suite, /* IN */
359+
const char *name, /* IN */
360+
TestFuncWC func, /* IN */
361+
TestFuncDtor dtor, /* IN */
362+
void *ctx,
363+
...) /* IN */
344364
{
345365
va_list ap;
346366

@@ -640,6 +660,7 @@ TestSuite_PrintHelp(TestSuite *suite) /* IN */
640660
"Options:\n"
641661
" -h, --help Show this help menu.\n"
642662
" --list-tests Print list of available tests.\n"
663+
" --tests-cmake Print CMake code that defines test information.\n"
643664
" -f, --no-fork Do not spawn a process per test (abort on "
644665
"first error).\n"
645666
" -l, --match PATTERN Run test by name, e.g. \"/Client/command\" or "
@@ -668,6 +689,18 @@ TestSuite_PrintTests(TestSuite *suite) /* IN */
668689
printf("\n");
669690
}
670691

692+
static void
693+
TestSuite_PrintCMake(TestSuite *suite)
694+
{
695+
printf("set(MONGOC_TESTS)\n");
696+
mlib_vec_foreach (Test, t, suite->tests) {
697+
printf("list(APPEND MONGOC_TESTS [[%.*s]])\n", MSTR_FMT(t->name));
698+
printf("set(MONGOC_TEST_%.*s_TAGS)\n", MSTR_FMT(t->name));
699+
mlib_vec_foreach (mstr, tag, t->tags) {
700+
printf("list(APPEND MONGOC_TEST_%.*s_TAGS [[%.*s]])\n", MSTR_FMT(t->name), MSTR_FMT(*tag));
701+
}
702+
}
703+
}
671704

672705
static void
673706
TestSuite_PrintJsonSystemHeader(FILE *stream)
@@ -989,7 +1022,11 @@ TestSuite_Run(TestSuite *suite) /* IN */
9891022
TestSuite_PrintTests(suite);
9901023
}
9911024

992-
if ((suite->flags & TEST_HELPTEXT) || (suite->flags & TEST_LISTTESTS)) {
1025+
if (suite->flags & TEST_TESTS_CMAKE) {
1026+
TestSuite_PrintCMake(suite);
1027+
}
1028+
1029+
if ((suite->flags & TEST_HELPTEXT) || (suite->flags & TEST_LISTTESTS) || (suite->flags & TEST_TESTS_CMAKE)) {
9931030
return 0;
9941031
}
9951032

src/libmongoc/tests/TestSuite.h

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ bson_open(const char *filename, int flags, ...)
9696
#define TEST_DEBUGOUTPUT (1 << 3)
9797
#define TEST_TRACE (1 << 4)
9898
#define TEST_LISTTESTS (1 << 5)
99+
#define TEST_TESTS_CMAKE (1 << 6)
99100

100101

101102
#define CERT_CA CERT_TEST_DIR "/ca.pem"
@@ -671,6 +672,10 @@ struct Test {
671672
* @brief The C string that names the test case
672673
*/
673674
mstr name;
675+
/**
676+
* @brief Set of tags that are associated with this test case
677+
*/
678+
mstr_vec tags;
674679
/**
675680
* @brief The function that will be executed for the test case
676681
*/
@@ -704,6 +709,7 @@ Test_Destroy(Test *t)
704709
t->dtor(t->ctx);
705710
}
706711
mstr_delete(t->name);
712+
mstr_vec_destroy(&t->tags);
707713
CheckFuncVec_destroy(&t->checks);
708714
}
709715

@@ -764,11 +770,10 @@ struct _TestFnCtx {
764770
TestFuncDtor dtor;
765771
};
766772

767-
768773
void
769774
TestSuite_Init(TestSuite *suite, const char *name, int argc, char **argv);
770775
void
771-
TestSuite_Add(TestSuite *suite, const char *name, TestFunc func);
776+
TestSuite_Add(TestSuite *suite, const char *name_and_tags, TestFunc func);
772777
int
773778
TestSuite_CheckLive(void);
774779
void
@@ -779,21 +784,22 @@ void
779784
_TestSuite_AddMockServerTest(TestSuite *suite, const char *name, TestFunc func, ...);
780785
#define TestSuite_AddMockServerTest(_suite, _name, ...) _TestSuite_AddMockServerTest(_suite, _name, __VA_ARGS__, NULL)
781786
void
782-
TestSuite_AddWC(TestSuite *suite, const char *name, TestFuncWC func, TestFuncDtor dtor, void *ctx);
787+
TestSuite_AddWC(TestSuite *suite, const char *name_and_tags, TestFuncWC func, TestFuncDtor dtor, void *ctx);
783788
Test *
784-
_V_TestSuite_AddFull(TestSuite *suite, const char *name, TestFuncWC func, TestFuncDtor dtor, void *ctx, va_list ap);
789+
_V_TestSuite_AddFull(
790+
TestSuite *suite, const char *name_and_tags, TestFuncWC func, TestFuncDtor dtor, void *ctx, va_list ap);
785791
void
786-
_TestSuite_AddFull(TestSuite *suite, const char *name, TestFuncWC func, TestFuncDtor dtor, void *ctx, ...);
792+
TestSuite_AddFull(TestSuite *suite, const char *name_and_tags, TestFuncWC func, TestFuncDtor dtor, void *ctx, ...);
787793
void
788794
_TestSuite_TestFnCtxDtor(void *ctx);
789795
#define TestSuite_AddFull(_suite, _name, _func, _dtor, _ctx, ...) \
790-
_TestSuite_AddFull(_suite, _name, _func, _dtor, _ctx, __VA_ARGS__, NULL)
791-
#define TestSuite_AddFullWithTestFn(_suite, _name, _func, _dtor, _test_fn, ...) \
792-
do { \
793-
TestFnCtx *ctx = bson_malloc(sizeof(TestFnCtx)); \
794-
ctx->test_fn = (TestFunc)(_test_fn); \
795-
ctx->dtor = _dtor; \
796-
_TestSuite_AddFull(_suite, _name, _func, _TestSuite_TestFnCtxDtor, ctx, __VA_ARGS__, NULL); \
796+
(TestSuite_AddFull)(_suite, _name, _func, _dtor, _ctx, __VA_ARGS__, NULL)
797+
#define TestSuite_AddFullWithTestFn(_suite, _name, _func, _dtor, _test_fn, ...) \
798+
do { \
799+
TestFnCtx *ctx = bson_malloc(sizeof(TestFnCtx)); \
800+
ctx->test_fn = (TestFunc)(_test_fn); \
801+
ctx->dtor = _dtor; \
802+
TestSuite_AddFull(_suite, _name, _func, _TestSuite_TestFnCtxDtor, ctx, __VA_ARGS__, NULL); \
797803
} while (0)
798804
int
799805
TestSuite_Run(TestSuite *suite);

src/libmongoc/tests/test-mcd-azure-imds.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,10 @@ test_mcd_azure_imds_install(TestSuite *suite)
107107
{
108108
TestSuite_Add(suite, "/azure/imds/http/parse", _test_oauth_parse);
109109
TestSuite_Add(suite, "/azure/imds/http/request", _test_http_req);
110-
TestSuite_AddFull(suite, "/azure/imds/http/talk", _test_with_mock_server, NULL, NULL, have_mock_server_env);
110+
TestSuite_AddFull(suite,
111+
"/azure/imds/http/talk [uses:fake_kms_provider_server]",
112+
_test_with_mock_server,
113+
NULL,
114+
NULL,
115+
have_mock_server_env);
111116
}

0 commit comments

Comments
 (0)