1.3.49
 
Loading...
Searching...
No Matches
doctest_utils.h
1#ifndef DOCTEST_UTILS_H
2#define DOCTEST_UTILS_H
3
4#include <doctest.h>
5#include <iostream>
6#include <sstream>
7#include <string>
8#include <vector>
9
10namespace helios {
11
22 inline bool validateDoctestArguments(int argc, char **argv) {
23 // List of valid doctest arguments (short and long forms)
24 static const std::vector<std::string> valid_prefixes = {"-?", "--help", "-h", "-v", "--version", "-c", "--count", "-ltc", "--list-test-cases", "-lts", "--list-test-suites", "-lr", "--list-reporters", "-tc", "--test-case", "-tce",
25 "--test-case-exclude", "-sf", "--source-file", "-sfe", "--source-file-exclude", "-ts", "--test-suite", "-tse", "--test-suite-exclude", "-sc", "--subcase", "-sce", "--subcase-exclude",
26 "-r", "--reporters", "-o", "--out", "-ob", "--order-by", "-rs", "--rand-seed", "-f", "--first", "-l", "--last", "-aa", "--abort-after", "-scfl", "--subcase-filter-levels",
27 // dt- prefixed versions
28 "-dt-?", "--dt-help", "-dt-h", "-dt-v", "--dt-version", "-dt-c", "--dt-count", "-dt-ltc", "--dt-list-test-cases", "-dt-lts", "--dt-list-test-suites", "-dt-lr", "--dt-list-reporters",
29 "-dt-tc", "--dt-test-case", "-dt-tce", "--dt-test-case-exclude", "-dt-sf", "--dt-source-file", "-dt-sfe", "--dt-source-file-exclude", "-dt-ts", "--dt-test-suite", "-dt-tse",
30 "--dt-test-suite-exclude", "-dt-sc", "--dt-subcase", "-dt-sce", "--dt-subcase-exclude", "-dt-r", "--dt-reporters", "-dt-o", "--dt-out", "-dt-ob", "--dt-order-by", "-dt-rs",
31 "--dt-rand-seed", "-dt-f", "--dt-first", "-dt-l", "--dt-last", "-dt-aa", "--dt-abort-after", "-dt-scfl", "--dt-subcase-filter-levels"};
32
33 auto isValidDoctestArgument = [&](const std::string &arg) -> bool {
34 // Check for exact matches or prefix matches with =
35 for (const auto &prefix: valid_prefixes) {
36 if (arg == prefix || (arg.length() > prefix.length() && arg.substr(0, prefix.length()) == prefix && arg[prefix.length()] == '=')) {
37 return true;
38 }
39 }
40 return false;
41 };
42
43 // Validate command line arguments
44 std::vector<std::string> invalid_args;
45 for (int i = 1; i < argc; ++i) { // Skip program name (argv[0])
46 std::string arg(argv[i]);
47 if (!isValidDoctestArgument(arg)) {
48 invalid_args.push_back(arg);
49 }
50 }
51
52 // Warn about invalid arguments
53 if (!invalid_args.empty()) {
54 std::cerr << "WARNING: Invalid or unrecognized test arguments detected:" << std::endl;
55 for (const auto &invalid_arg: invalid_args) {
56 std::cerr << " " << invalid_arg << std::endl;
57 }
58 std::cerr << std::endl;
59 std::cerr << "Common valid patterns:" << std::endl;
60 std::cerr << " -tc=\"pattern\" : Run tests matching pattern" << std::endl;
61 std::cerr << " --test-case=\"pattern\" : Run tests matching pattern" << std::endl;
62 std::cerr << " -c : Count matching tests" << std::endl;
63 std::cerr << " --help : Show full help" << std::endl;
64 std::cerr << std::endl;
65
66 // Try to suggest corrections for common mistakes
67 for (const auto &invalid_arg: invalid_args) {
68 if (!invalid_arg.empty() && invalid_arg[0] != '-') {
69 std::cerr << "Suggestion: Did you mean -tc=\"" << invalid_arg << "\" ?" << std::endl;
70 }
71 }
72 std::cerr << std::endl;
73 std::cerr << "Proceeding with test execution..." << std::endl;
74 std::cerr << std::endl;
75
76 return false;
77 }
78
79 return true;
80 }
81
92 inline bool checkTestFiltersMatchTests(int argc, char **argv) {
93 // Create a separate context to count matching tests
94 doctest::Context count_context;
95 count_context.applyCommandLine(argc, argv);
96
97 // Add the count option to see how many tests match
98 count_context.setOption("count", true);
99 count_context.setOption("no-run", true);
100
101 // Capture the output by redirecting cout temporarily
102 std::ostringstream count_output;
103 std::streambuf *orig_cout = std::cout.rdbuf();
104 std::cout.rdbuf(count_output.rdbuf());
105
106 // Run in count mode
107 int count_result = count_context.run();
108
109 // Restore cout
110 std::cout.rdbuf(orig_cout);
111
112 // Parse the output to get the count
113 std::string output = count_output.str();
114
115 // Look for patterns indicating 0 tests found
116 if (output.find("unskipped test cases passing the current filters: 0") != std::string::npos || output.find("number of tests: 0") != std::string::npos) {
117 // Check if any test filters were actually specified
118 bool has_test_filters = false;
119 for (int i = 1; i < argc; ++i) {
120 std::string arg(argv[i]);
121 if (arg.find("-tc=") == 0 || arg.find("--test-case=") == 0 || arg.find("-ts=") == 0 || arg.find("--test-suite=") == 0 || arg.find("-sc=") == 0 || arg.find("--subcase=") == 0 || arg.find("-dt-tc=") == 0 ||
122 arg.find("--dt-test-case=") == 0 || arg.find("-dt-ts=") == 0 || arg.find("--dt-test-suite=") == 0 || arg.find("-dt-sc=") == 0 || arg.find("--dt-subcase=") == 0) {
123 has_test_filters = true;
124 break;
125 }
126 }
127
128 if (has_test_filters) {
129 std::cerr << "WARNING: No tests match the specified filters!" << std::endl;
130 std::cerr << "Your filter criteria resulted in 0 matching tests." << std::endl;
131 std::cerr << "Use -ltc or --list-test-cases to see available tests." << std::endl;
132 std::cerr << std::endl;
133 return false;
134 }
135 }
136
137 return true;
138 }
139
151 template<typename ContextType = doctest::Context>
152 inline int runDoctestWithValidation(int argc, char **argv) {
153 // Validate arguments first
154 validateDoctestArguments(argc, argv);
155
156 // Check if test filters match any tests
157 checkTestFiltersMatchTests(argc, argv);
158
159 // Run the tests with command line arguments
160 ContextType context;
161 context.applyCommandLine(argc, argv);
162 int res = context.run();
163
164 if (context.shouldExit()) {
165 return res;
166 }
167
168 return res;
169 }
170
171} // namespace helios
172
173#endif // DOCTEST_UTILS_H