pairinteraction
A Rydberg Interaction Calculator
run_unit_tests.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2024 Pairinteraction Developers
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#define DOCTEST_CONFIG_IMPLEMENT
5
7
12
13#include <cstdlib>
14#include <doctest/doctest.h>
15#include <filesystem>
16#include <httplib.h>
17#include <mutex>
18#include <spdlog/spdlog.h>
19
20// Create a reporter for doctest that logs to spdlog
21namespace doctest {
22
23// The code of the LoggingReporter is based on the ConsoleReporter from doctest,
24// https://github.com/doctest/doctest/blob/ae7a13539fb71f270b87eb2e874fbac80bc8dda2/doctest/parts/doctest.cpp#L2868.
25//
26// SPDX-SnippetBegin
27// SPDX-FileCopyrightText: (c) 2016-2025 Viktor Kirilov, Sebastian Weber
28// SPDX-License-Identifier: MIT
29
30// NOLINTBEGIN(cppcoreguidelines-macro-usage)
31#define DOCTEST_LOCK_MUTEX(name) \
32 std::lock_guard<std::mutex> DOCTEST_ANONYMOUS(DOCTEST_ANON_LOCK_)(name);
33// NOLINTEND(cppcoreguidelines-macro-usage)
34
36 LoggingReporter(const ContextOptions &co) : ConsoleReporter(co) {}
37
38 void log_contexts() {}
39
40 void logTestStart() {}
41
42 void test_run_end(const TestRunStats &p) override {
43 if (opt.minimal && p.numTestCasesFailed == 0) {
44 return;
45 }
46
47 std::stringstream ss;
48 ss << Color::Yellow
49 << "==============================================================================="
50 << Color::None << "\n";
51 ss << std::dec;
52
53 auto totwidth =
54 int(std::ceil(log10(static_cast<double>(std::max(p.numTestCasesPassingFilters,
55 static_cast<unsigned>(p.numAsserts))) +
56 1)));
57 auto passwidth =
58 int(std::ceil(log10(static_cast<double>(std::max(
59 p.numTestCasesPassingFilters - p.numTestCasesFailed,
60 static_cast<unsigned>(p.numAsserts - p.numAssertsFailed))) +
61 1)));
62 auto failwidth = int(
63 std::ceil(log10(static_cast<double>(std::max(
64 p.numTestCasesFailed, static_cast<unsigned>(p.numAssertsFailed))) +
65 1)));
66 const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0;
67 ss << "test cases: " << std::setw(totwidth) << p.numTestCasesPassingFilters << " | "
68 << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : Color::Green)
69 << std::setw(passwidth) << p.numTestCasesPassingFilters - p.numTestCasesFailed
70 << " passed" << Color::None << " | "
71 << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth)
72 << p.numTestCasesFailed << " failed" << Color::None << " |";
73 if (!opt.no_skipped_summary) {
74 const unsigned int numSkipped = p.numTestCases - p.numTestCasesPassingFilters;
75 ss << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped << " skipped"
76 << Color::None;
77 }
78 ss << "\n";
79 ss << "assertions: " << std::setw(totwidth) << p.numAsserts << " | "
80 << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green)
81 << std::setw(passwidth) << (p.numAsserts - p.numAssertsFailed) << " passed"
82 << Color::None << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None)
83 << std::setw(failwidth) << p.numAssertsFailed << " failed" << Color::None << " |\n";
84 ss << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green)
85 << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl;
86
87 if (p.numTestCasesFailed > 0) {
88 for (std::string line; std::getline(ss, line);) {
89 SPDLOG_ERROR(line);
90 }
91 } else {
92 for (std::string line; std::getline(ss, line);) {
93 SPDLOG_INFO(line);
94 }
95 }
96 }
97
98 void test_case_end(const CurrentTestCaseStats &st) override {
99 if (tc->m_no_output) {
100 return;
101 }
102
103 if (opt.duration ||
104 (st.failure_flags != 0 &&
105 st.failure_flags != static_cast<int>(TestCaseFailureReason::AssertFailure))) {
106 logTestStart();
107 }
108
109 if (opt.duration) {
110 std::stringstream ss;
111 ss << std::setprecision(6) << std::fixed << st.seconds << " s: " << tc->m_name;
112 SPDLOG_INFO(ss.str());
113 }
114
115 if ((st.failure_flags & TestCaseFailureReason::Timeout) != 0) {
116 std::stringstream ss;
117 ss << Color::Red << "Test case exceeded time limit of " << std::setprecision(6)
118 << std::fixed << tc->m_timeout << "!" << Color::None;
119 SPDLOG_ERROR(ss.str());
120 }
121
122 if ((st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) != 0) {
123 std::stringstream ss;
124 ss << Color::Red << "Should have failed but didn't! Marking it as failed!"
125 << Color::None;
126 SPDLOG_ERROR(ss.str());
127 } else if ((st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) != 0) {
128 std::stringstream ss;
129 ss << Color::Yellow << "Failed as expected so marking it as not failed" << Color::None;
130 SPDLOG_WARN(ss.str());
131 } else if ((st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) != 0) {
132 std::stringstream ss;
133 ss << Color::Yellow << "Allowed to fail so marking it as not failed" << Color::None;
134 SPDLOG_WARN(ss.str());
135 } else if ((st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) != 0) {
136 std::stringstream ss;
137 ss << Color::Red << "Didn't fail exactly " << tc->m_expected_failures
138 << " times so marking it as failed!" << Color::None;
139 SPDLOG_ERROR(ss.str());
140 } else if ((st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) != 0) {
141 std::stringstream ss;
142 ss << Color::Yellow << "Failed exactly " << tc->m_expected_failures
143 << " times as expected so marking it as not failed!" << Color::None;
144 SPDLOG_WARN(ss.str());
145 }
146
147 if ((st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) != 0) {
148 std::stringstream ss;
149 ss << Color::Red << "Aborting - too many failed asserts!" << Color::None;
150 SPDLOG_ERROR(ss.str());
151 }
152 }
153
154 void test_case_exception(const TestCaseException &e) override {
155 if (tc->m_no_output) {
156 return;
157 }
158
159 DOCTEST_LOCK_MUTEX(mutex)
160
161 logTestStart();
162
163 std::stringstream ss;
164 ss << "[" << skipPathFromFilename(tc->m_file.c_str()) << (opt.gnu_file_line ? ":" : "(")
165 << (opt.no_line_numbers ? 0 : tc->m_line) << (opt.gnu_file_line ? "" : ")") << "] ";
166 std::string loc = ss.str();
167 ss.str("");
168 ss << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ")
169 << Color::None << e.error_string;
170 for (std::string line; std::getline(ss, line);) {
171 SPDLOG_ERROR(loc + line);
172 }
173 }
174
175 void log_assert(const AssertData &rb) override {
176 if ((!rb.m_failed && !opt.success) || tc->m_no_output) {
177 return;
178 }
179
180 DOCTEST_LOCK_MUTEX(mutex)
181
182 logTestStart();
183
184 std::stringstream ss;
185 ss << "[" << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(")
186 << (opt.no_line_numbers ? 0 : rb.m_line) << (opt.gnu_file_line ? "" : ")") << "] ";
187 std::string loc = ss.str();
188 ss.str("");
189 fulltext_log_assert_to_stream(ss, rb);
190 if (rb.m_failed) {
191 for (std::string line; std::getline(ss, line);) {
192 SPDLOG_ERROR(loc + line);
193 }
194 } else {
195 for (std::string line; std::getline(ss, line);) {
196 SPDLOG_INFO(loc + line);
197 }
198 }
199
200 log_contexts();
201 }
202
203 void log_message(const MessageData &mb) override {
204 if (tc->m_no_output) {
205 return;
206 }
207
208 DOCTEST_LOCK_MUTEX(mutex)
209
210 logTestStart();
211
212 std::stringstream ss;
213 ss << "[" << skipPathFromFilename(mb.m_file) << (opt.gnu_file_line ? ":" : "(")
214 << (opt.no_line_numbers ? 0 : mb.m_line) << (opt.gnu_file_line ? "" : ")") << "] ";
215 std::string loc = ss.str();
216 ss.str("");
217 ss << getSuccessOrFailColor(false, mb.m_severity)
218 << getSuccessOrFailString((mb.m_severity & assertType::is_warn) != 0, mb.m_severity,
219 "MESSAGE")
220 << ": " << Color::None << mb.m_string;
221 for (std::string line; std::getline(ss, line);) {
222 SPDLOG_INFO(loc + line);
223 }
224
225 log_contexts();
226 }
227};
228
229// SPDX-SnippetEnd
230
232} // namespace doctest
233
234constexpr std::string_view OS_NAME =
235#if defined(_WIN32)
236 "Windows";
237#elif defined(__APPLE__)
238 "macOS";
239#elif defined(__linux__)
240 "Linux";
241#else
242 "Unknown";
243#endif
244
245namespace pairinteraction {
246int run_unit_tests(int argc, char **argv, bool download_missing, bool use_cache,
247 std::filesystem::path database_dir) {
248
249 // Setup the tests
250 doctest::Context ctx;
251 ctx.setOption("abort-after", 5);
252 ctx.setOption("no-run", 0);
253 ctx.setOption("duration", true);
254 ctx.setOption("no-path-filenames", true);
255 ctx.applyCommandLine(argc, argv);
256 ctx.setOption("no-colors", true);
257 ctx.setOption("no-breaks", true);
258 ctx.setOption("reporters", "logging");
259 ctx.setOption("no-intro", true);
260
261 // Log the version and system information
262 SPDLOG_INFO("Version of pairinteraction: {}.{}.{}", VERSION_MAJOR, VERSION_MINOR,
264 SPDLOG_INFO("Operating system: {}", OS_NAME);
265
266 // Create a global database instance and run the tests
267 Database::get_global_instance(download_missing, use_cache, std::move(database_dir));
268 int exitcode = ctx.run();
269
271 SPDLOG_INFO("The log was stored to {}", logdir.string());
272
273 if (exitcode != 0) {
274 if (download_missing) {
275 httplib::Client client("https://www.github.com");
276 auto res = client.Head("/");
277 if (!res) {
278 SPDLOG_ERROR(
279 "Test failed. Please check your internet connection. An internet "
280 "connection is required to download databases of atomic states and matrix "
281 "elements if they are not available locally. The log was stored to {}",
282 logdir.string());
283 } else {
284 SPDLOG_ERROR(
285 "Tests failed. Consider creating an issue on "
286 "https://github.com/pairinteraction/pairinteraction/issues, attaching the "
287 "log. The log was stored to {}",
288 logdir.string());
289 }
290 } else {
291 SPDLOG_ERROR(
292 "Tests failed. Consider creating an issue on "
293 "https://github.com/pairinteraction/pairinteraction/issues, attaching the "
294 "log. If the tests failed because of unavailable states or "
295 "matrix elements, consider downloading missing databases by calling "
296 "the test function with 'download_missing = true'. The log was stored to {}",
297 logdir.string());
298 }
299 }
300
301 return exitcode;
302};
303} // namespace pairinteraction
static Database & get_global_instance()
Definition: Database.cpp:1068
REGISTER_REPORTER("logging", 1, doctest::LoggingReporter)
std::filesystem::path get_cache_directory()
Definition: paths.hpp:29
constexpr int VERSION_MINOR
Definition: version.hpp:9
constexpr int VERSION_PATCH
Definition: version.hpp:10
constexpr int VERSION_MAJOR
Definition: version.hpp:8
int run_unit_tests(int argc=0, char **argv={}, bool download_missing=false, bool use_cache=true, std::filesystem::path database_dir="")
#define DOCTEST_LOCK_MUTEX(name)
constexpr std::string_view OS_NAME
LoggingReporter(const ContextOptions &co)
void log_message(const MessageData &mb) override
void test_case_end(const CurrentTestCaseStats &st) override
void log_assert(const AssertData &rb) override
void test_case_exception(const TestCaseException &e) override
void test_run_end(const TestRunStats &p) override