1.3.72
 
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 // Reference: doctest.h parseOption/parseFlag/DOCTEST_PARSE_AS_BOOL_OR_FLAG calls
25 static const std::vector<std::string> valid_prefixes = {
26 // Help, version, counting, listing
27 "-?", "--help", "-h", "-v", "--version", "-c", "--count",
28 "-ltc", "--list-test-cases", "-lts", "--list-test-suites", "-lr", "--list-reporters",
29 // Filters (comma-separated args)
30 "-tc", "--test-case", "-tce", "--test-case-exclude",
31 "-sf", "--source-file", "-sfe", "--source-file-exclude",
32 "-ts", "--test-suite", "-tse", "--test-suite-exclude",
33 "-sc", "--subcase", "-sce", "--subcase-exclude",
34 "-r", "--reporters",
35 // String/int options
36 "-o", "--out", "-ob", "--order-by", "-rs", "--rand-seed",
37 "-f", "--first", "-l", "--last", "-aa", "--abort-after",
38 "-scfl", "--subcase-filter-levels",
39 // Bool/flag options
40 "-s", "--success", "-cs", "--case-sensitive", "-e", "--exit",
41 "-d", "--duration", "-m", "--minimal", "-q", "--quiet",
42 "-nt", "--no-throw", "-ne", "--no-exitcode", "-nr", "--no-run",
43 "-ni", "--no-intro", "-nv", "--no-version", "-nc", "--no-colors",
44 "-fc", "--force-colors", "-nb", "--no-breaks", "-ns", "--no-skip",
45 "-gfl", "--gnu-file-line", "-npf", "--no-path-filenames",
46 "-sfp", "--strip-file-prefixes", "-nln", "--no-line-numbers",
47 "-ndo", "--no-debug-output", "-nss", "--no-skipped-summary",
48 "-ntio", "--no-time-in-output",
49 // dt- prefixed versions (help, version, counting, listing)
50 "-dt-?", "--dt-help", "-dt-h", "-dt-v", "--dt-version", "-dt-c", "--dt-count",
51 "-dt-ltc", "--dt-list-test-cases", "-dt-lts", "--dt-list-test-suites", "-dt-lr", "--dt-list-reporters",
52 // dt- prefixed filters
53 "-dt-tc", "--dt-test-case", "-dt-tce", "--dt-test-case-exclude",
54 "-dt-sf", "--dt-source-file", "-dt-sfe", "--dt-source-file-exclude",
55 "-dt-ts", "--dt-test-suite", "-dt-tse", "--dt-test-suite-exclude",
56 "-dt-sc", "--dt-subcase", "-dt-sce", "--dt-subcase-exclude",
57 "-dt-r", "--dt-reporters",
58 // dt- prefixed string/int options
59 "-dt-o", "--dt-out", "-dt-ob", "--dt-order-by", "-dt-rs", "--dt-rand-seed",
60 "-dt-f", "--dt-first", "-dt-l", "--dt-last", "-dt-aa", "--dt-abort-after",
61 "-dt-scfl", "--dt-subcase-filter-levels",
62 // dt- prefixed bool/flag options
63 "-dt-s", "--dt-success", "-dt-cs", "--dt-case-sensitive", "-dt-e", "--dt-exit",
64 "-dt-d", "--dt-duration", "-dt-m", "--dt-minimal", "-dt-q", "--dt-quiet",
65 "-dt-nt", "--dt-no-throw", "-dt-ne", "--dt-no-exitcode", "-dt-nr", "--dt-no-run",
66 "-dt-ni", "--dt-no-intro", "-dt-nv", "--dt-no-version", "-dt-nc", "--dt-no-colors",
67 "-dt-fc", "--dt-force-colors", "-dt-nb", "--dt-no-breaks", "-dt-ns", "--dt-no-skip",
68 "-dt-gfl", "--dt-gnu-file-line", "-dt-npf", "--dt-no-path-filenames",
69 "-dt-sfp", "--dt-strip-file-prefixes", "-dt-nln", "--dt-no-line-numbers",
70 "-dt-ndo", "--dt-no-debug-output", "-dt-nss", "--dt-no-skipped-summary",
71 "-dt-ntio", "--dt-no-time-in-output"};
72
73 auto isValidDoctestArgument = [&](const std::string &arg) -> bool {
74 // Check for exact matches or prefix matches with =
75 for (const auto &prefix: valid_prefixes) {
76 if (arg == prefix || (arg.length() > prefix.length() && arg.substr(0, prefix.length()) == prefix && arg[prefix.length()] == '=')) {
77 return true;
78 }
79 }
80 return false;
81 };
82
83 // Validate command line arguments
84 std::vector<std::string> invalid_args;
85 for (int i = 1; i < argc; ++i) { // Skip program name (argv[0])
86 std::string arg(argv[i]);
87 if (!isValidDoctestArgument(arg)) {
88 invalid_args.push_back(arg);
89 }
90 }
91
92 // Warn about invalid arguments
93 if (!invalid_args.empty()) {
94 std::cerr << "WARNING: Invalid or unrecognized test arguments detected:" << std::endl;
95 for (const auto &invalid_arg: invalid_args) {
96 std::cerr << " " << invalid_arg << std::endl;
97 }
98 std::cerr << std::endl;
99 std::cerr << "Common valid patterns:" << std::endl;
100 std::cerr << " -tc=\"pattern\" : Run tests matching pattern" << std::endl;
101 std::cerr << " --test-case=\"pattern\" : Run tests matching pattern" << std::endl;
102 std::cerr << " -s : Include successful assertions in output" << std::endl;
103 std::cerr << " -c : Count matching tests" << std::endl;
104 std::cerr << " --help : Show full help" << std::endl;
105 std::cerr << std::endl;
106
107 // Try to suggest corrections for common mistakes
108 for (const auto &invalid_arg: invalid_args) {
109 if (!invalid_arg.empty() && invalid_arg[0] != '-') {
110 std::cerr << "Suggestion: Did you mean -tc=\"" << invalid_arg << "\" ?" << std::endl;
111 }
112 }
113 std::cerr << std::endl;
114 std::cerr << "Proceeding with test execution..." << std::endl;
115 std::cerr << std::endl;
116
117 return false;
118 }
119
120 return true;
121 }
122
133 inline bool checkTestFiltersMatchTests(int argc, char **argv) {
134 // Create a separate context to count matching tests
135 doctest::Context count_context;
136 count_context.applyCommandLine(argc, argv);
137
138 // Add the count option to see how many tests match
139 count_context.setOption("count", true);
140 count_context.setOption("no-run", true);
141
142 // Capture the output by redirecting cout temporarily
143 std::ostringstream count_output;
144 std::streambuf *orig_cout = std::cout.rdbuf();
145 std::cout.rdbuf(count_output.rdbuf());
146
147 // Run in count mode
148 int count_result = count_context.run();
149
150 // Restore cout
151 std::cout.rdbuf(orig_cout);
152
153 // Parse the output to get the count
154 std::string output = count_output.str();
155
156 // Look for patterns indicating 0 tests found
157 if (output.find("unskipped test cases passing the current filters: 0") != std::string::npos || output.find("number of tests: 0") != std::string::npos) {
158 // Check if any test filters were actually specified
159 bool has_test_filters = false;
160 for (int i = 1; i < argc; ++i) {
161 std::string arg(argv[i]);
162 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 ||
163 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) {
164 has_test_filters = true;
165 break;
166 }
167 }
168
169 if (has_test_filters) {
170 std::cerr << "WARNING: No tests match the specified filters!" << std::endl;
171 std::cerr << "Your filter criteria resulted in 0 matching tests." << std::endl;
172 std::cerr << "Use -ltc or --list-test-cases to see available tests." << std::endl;
173 std::cerr << std::endl;
174 return false;
175 }
176 }
177
178 return true;
179 }
180
192 template<typename ContextType = doctest::Context>
193 inline int runDoctestWithValidation(int argc, char **argv) {
194 // Validate arguments first
195 validateDoctestArguments(argc, argv);
196
197 // Check if test filters match any tests
198 checkTestFiltersMatchTests(argc, argv);
199
200 // Run the tests with command line arguments
201 ContextType context;
202 context.applyCommandLine(argc, argv);
203 int res = context.run();
204
205 if (context.shouldExit()) {
206 return res;
207 }
208
209 return res;
210 }
211
212} // namespace helios
213
214#endif // DOCTEST_UTILS_H