mimic++ v10.0.1
Loading...
Searching...
No Matches
StringifyReports.hpp
Go to the documentation of this file.
1// Copyright Dominic (DNKpp) Koepke 2024 - 2026.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at
4// https://www.boost.org/LICENSE_1_0.txt)
5
6#ifndef MIMICPP_REPORTING_STRINGIFY_REPORTS_HPP
7#define MIMICPP_REPORTING_STRINGIFY_REPORTS_HPP
8
9#pragma once
10
11#include "mimic++/Fwd.hpp"
21
22#ifndef MIMICPP_DETAIL_IS_MODULE
23 #include <algorithm>
24 #include <exception>
25 #include <iterator>
26 #include <optional>
27 #include <span>
28 #include <utility>
29#endif
30
31namespace mimicpp::reporting::detail
32{
33 template <print_iterator OutIter>
34 OutIter stringify_stacktrace(OutIter out, util::Stacktrace const& stacktrace)
35 {
36 if (!stacktrace.empty())
37 {
38 out = format::format_to(std::move(out), "\nStacktrace:\n");
39 out = mimicpp::print(std::move(out), stacktrace);
40 }
41
42 return out;
43 }
44
45 template <print_iterator OutIter>
46 OutIter stringify_call_report_arguments(OutIter out, CallReport const& call, StringViewT const linePrefix)
47 {
48 if (call.argDetails.empty())
49 {
50 return out;
51 }
52
53 out = std::ranges::copy(linePrefix, std::move(out)).out;
54 out = format::format_to(std::move(out), "Where:\n");
55
56 for (int i{};
57 auto const& [type, state] : call.argDetails)
58 {
59 out = std::ranges::copy(linePrefix, std::move(out)).out;
60 out = format::format_to(std::move(out), "\targ[{}] => ", i++);
61 out = std::ranges::copy(type.name(), std::move(out)).out;
62 out = format::format_to(std::move(out), ": ");
63 out = std::ranges::copy(state, std::move(out)).out;
64 out = format::format_to(std::move(out), "\n");
65 }
66
67 return out;
68 }
69
70 template <print_iterator OutIter>
71 OutIter stringify_call_report_from(OutIter out, CallReport const& call)
72 {
73 out = format::format_to(std::move(out), "Call originated from ");
74
75 if (!call.stacktrace.empty())
76 {
77 out = util::stacktrace::detail::print_entry(std::move(out), call.stacktrace, 0u);
78 }
79 else
80 {
81 out = mimicpp::print(std::move(out), call.fromLoc);
82 }
83 out = format::format_to(std::move(out), "\n");
84
85 return out;
86 }
87
88 template <print_iterator OutIter>
89 OutIter stringify_call_report_target(OutIter out, CallReport const& call)
90 {
92 std::move(out),
93 "\tOn Target `{}` used Overload `{}`\n",
94 call.target.name,
95 call.target.overloadReport.name());
96
97 return out;
98 }
99
100 template <print_iterator OutIter>
101 OutIter stringify_expectation_report_from(OutIter out, ExpectationReport const& expectation)
102 {
103 out = format::format_to(std::move(out), "Expectation defined at ");
104 out = mimicpp::print(std::move(out), expectation.from);
105 out = format::format_to(std::move(out), "\n");
106
107 return out;
108 }
109
110 template <print_iterator OutIter>
111 OutIter stringify_expectation_report_target(OutIter out, ExpectationReport const& expectation)
112 {
113 out = format::format_to(
114 std::move(out),
115 "\tOf Target `{}` related to Overload `{}`\n",
116 expectation.target.name,
117 expectation.target.overloadReport.name());
118
119 return out;
120 }
121
122 template <print_iterator OutIter>
123 OutIter stringify_expectation_report_requirement_adherences(
124 OutIter out,
125 std::span<expectation::MatchResult const> const adherences,
126 StringViewT const linePrefix)
127 {
128 if (adherences.empty())
129 {
130 return out;
131 }
132
133 out = std::ranges::copy(linePrefix, std::move(out)).out;
134 out = format::format_to(std::move(out), "With Adherence(s):\n");
135
136 for (auto const& addherence : adherences)
137 {
138 auto const& [description] = std::get<expectation::MatchSuccess>(addherence);
139 if (description)
140 {
141 out = std::ranges::copy(linePrefix, std::move(out)).out;
142 out = format::format_to(std::move(out), " + ");
143 out = std::ranges::copy(*description, std::move(out)).out;
144 out = format::format_to(std::move(out), "\n");
145 }
146 }
147
148 return out;
149 }
150
151 struct inapplicable_reason_printer
152 {
153 template <print_iterator OutIter>
154 OutIter operator()([[maybe_unused]] OutIter out, [[maybe_unused]] state_applicable const& state) const
155 {
157 }
158
159 template <print_iterator OutIter>
160 constexpr OutIter operator()(OutIter out, state_inapplicable const& state) const
161 {
162 auto const totalSequences = std::ranges::ssize(state.sequences)
163 + std::ranges::ssize(state.inapplicableSequences);
164 out = format::format_to(
165 std::move(out),
166 "it's not Head of {} Sequence(s) ({} total).\n",
167 std::ranges::ssize(state.inapplicableSequences),
168 totalSequences);
169
170 MIMICPP_ASSERT(!state.inapplicableSequences.empty(), "Inapplicable sequences can not be empty.");
171 for (auto const& sequence : state.inapplicableSequences)
172 {
173 MIMICPP_ASSERT(sequence.headFrom, "Head-from can not be empty.");
174 out = format::format_to(std::move(out), "\t\tExpectation defined at ");
175 out = mimicpp::print(std::move(out), *sequence.headFrom);
176 out = format::format_to(std::move(out), " is Head of Sequence from ");
177 out = mimicpp::print(std::move(out), sequence.from);
178 out = format::format_to(std::move(out), "\n");
179 }
180
181 return out;
182 }
183
184 template <print_iterator OutIter>
185 constexpr OutIter operator()(OutIter out, state_saturated const& state) const
186 {
187 out = format::format_to(
188 std::move(out),
189 "it's already saturated (matched {} out of {} times).\n",
190 state.count,
191 state.max);
192
193 return out;
194 }
195 };
196
197 template <print_iterator OutIter>
198 OutIter stringify_expectation_report_requirement_violations(
199 OutIter out,
200 std::span<expectation::MatchResult const> const failures,
201 StringViewT const linePrefix)
202 {
203 MIMICPP_ASSERT(!failures.empty(), "Zero requirements can never be violated.");
204
205 out = std::ranges::copy(linePrefix, std::move(out)).out;
206 out = format::format_to(std::move(out), "Due to Violation(s):\n");
207
208 int withoutDescription{0};
209 for (auto const& failure : failures)
210 {
211 auto const& [description] = std::get<expectation::MatchFailure>(failure);
212 if (description)
213 {
214 out = std::ranges::copy(linePrefix, std::move(out)).out;
215 out = format::format_to(std::move(out), " - ");
216 out = std::ranges::copy(*description, std::move(out)).out;
217 out = format::format_to(std::move(out), "\n");
218 }
219 else
220 {
221 ++withoutDescription;
222 }
223 }
224
225 if (0 < withoutDescription)
226 {
227 out = std::ranges::copy(linePrefix, std::move(out)).out;
228 out = format::format_to(std::move(out), " - ");
229 out = format::format_to(
230 std::move(out),
231 "{} Requirement(s) failed without further description.\n",
232 withoutDescription);
233 }
234
235 return out;
236 }
237
238 struct unfulfilled_reason_printer
239 {
240 template <print_iterator OutIter>
241 OutIter operator()(OutIter out, state_applicable const& state) const
242 {
243 return stringify_times_state(
244 std::move(out),
245 state.count,
246 state.min,
247 state.max);
248 }
249
250 template <print_iterator OutIter>
251 OutIter operator()(OutIter out, state_inapplicable const& state) const
252 {
253 return stringify_times_state(
254 std::move(out),
255 state.count,
256 state.min,
257 state.max);
258 }
259
260 template <print_iterator OutIter>
261 OutIter operator()([[maybe_unused]] OutIter out, [[maybe_unused]] state_saturated const& state) const
262 {
264 }
265
266 private:
267 template <print_iterator OutIter>
268 static OutIter stringify_times_state(OutIter out, int const current, int const min, int const max)
269 {
270 const auto verbalizeValue = [](OutIter o, int const value) {
271 MIMICPP_ASSERT(0 < value, "Invalid value.");
272
273 switch (value)
274 {
275 case 1: return format::format_to(std::move(o), "once");
276 case 2: return format::format_to(std::move(o), "twice");
277 default: return format::format_to(std::move(o), "{} times", value);
278 }
279 };
280
281 MIMICPP_ASSERT(current < min, "State doesn't denote an unsatisfied state.");
282
283 out = format::format_to(std::move(out), "matching ");
284
285 if (min == max)
286 {
287 out = format::format_to(std::move(out), "exactly ");
288 out = verbalizeValue(std::move(out), min);
289 out = format::format_to(std::move(out), " ");
290 }
291 else if (max == std::numeric_limits<int>::max())
292 {
293 out = format::format_to(std::move(out), "at least ");
294 out = verbalizeValue(std::move(out), min);
295 out = format::format_to(std::move(out), " ");
296 }
297 else
298 {
299 out = format::format_to(
300 std::move(out),
301 "between {} and {} times ",
302 min,
303 max);
304 }
305
306 return format::format_to(
307 std::move(out),
308 "was expected => requires {} further match(es).\n",
309 min - current);
310 }
311 };
312}
313
315{
316 [[nodiscard]]
318 {
319 MIMICPP_ASSERT(std::holds_alternative<state_applicable>(match.expectationReport.controlReport), "Report denotes inapplicable expectation.");
320
321 StringStreamT ss{};
322 ss << "Matched ";
323 detail::stringify_call_report_from(std::ostreambuf_iterator{ss}, call);
324 detail::stringify_call_report_target(std::ostreambuf_iterator{ss}, call);
325 detail::stringify_call_report_arguments(std::ostreambuf_iterator{ss}, call, "\t");
326
327 ss << "\t" << "Chose ";
328 detail::stringify_expectation_report_from(std::ostreambuf_iterator{ss}, match.expectationReport);
329
330 if (!match.matchResults.empty())
331 {
332 std::ranges::sort(
333 match.matchResults,
334 {},
335 [](expectation::MatchResult const& m) -> auto const& { return std::get<expectation::MatchSuccess>(m).description; });
336 detail::stringify_expectation_report_requirement_adherences(
337 std::ostreambuf_iterator{ss},
338 match.matchResults,
339 "\t");
340 }
341 else
342 {
343 ss << "\t" << "Without any Requirements.\n";
344 }
345
346 detail::stringify_stacktrace(
347 std::ostreambuf_iterator{ss},
348 call.stacktrace);
349
350 return std::move(ss).str();
351 }
352
353 [[nodiscard]]
354 inline StringT stringify_inapplicable_matches(CallReport const& call, std::span<MatchReport> reports)
355 {
356 MIMICPP_ASSERT(!reports.empty(), "No expectations given.");
357
358 StringStreamT ss{};
359
360 ss << "Unmatched ";
361 detail::stringify_call_report_from(std::ostreambuf_iterator{ss}, call);
362 detail::stringify_call_report_target(std::ostreambuf_iterator{ss}, call);
363 detail::stringify_call_report_arguments(std::ostreambuf_iterator{ss}, call, "\t");
364
365 ss << reports.size() << " inapplicable but otherwise matching Expectation(s):";
366
367 for (int i{};
368 auto& report : reports)
369 {
370 auto& [expReport, matchReport] = report;
371 ss << "\n\t#" << ++i << " ";
372 detail::stringify_expectation_report_from(std::ostreambuf_iterator{ss}, expReport);
373
374 ss << "\tBecause ";
375 std::visit(
376 std::bind_front(detail::inapplicable_reason_printer{}, std::ostreambuf_iterator{ss}),
377 expReport.controlReport);
378
379 std::ranges::sort(
380 matchReport,
381 {},
382 [](expectation::MatchResult const& m) -> auto const& { return std::get<expectation::MatchSuccess>(m).description; });
383 detail::stringify_expectation_report_requirement_adherences(std::ostreambuf_iterator{ss}, matchReport, "\t");
384 }
385
386 detail::stringify_stacktrace(
387 std::ostreambuf_iterator{ss},
388 call.stacktrace);
389
390 return std::move(ss).str();
391 }
392
393 [[nodiscard]]
394 inline StringT stringify_no_matches(CallReport const& call, std::span<MatchReport> noMatchReports)
395 {
396 StringStreamT ss{};
397
398 ss << "Unmatched ";
399 detail::stringify_call_report_from(std::ostreambuf_iterator{ss}, call);
400 detail::stringify_call_report_target(std::ostreambuf_iterator{ss}, call);
401 detail::stringify_call_report_arguments(std::ostreambuf_iterator{ss}, call, "\t");
402
403 std::vector<MatchReport*> applicableReports{};
404 for (auto& noMatch : noMatchReports)
405 {
406 if (std::holds_alternative<state_applicable>(noMatch.expectationReport.controlReport))
407 {
408 applicableReports.emplace_back(&noMatch);
409 }
410 }
411
412 if (applicableReports.empty())
413 {
414 ss << "No applicable Expectations available!\n";
415 }
416 else
417 {
418 ss << applicableReports.size() << " applicable non-matching Expectation(s):";
419
420 for (int i{};
421 auto* report : applicableReports)
422 {
423 auto& [expReport, matchResults] = *report;
424
425 ss << "\n\t#" << ++i << " ";
426 detail::stringify_expectation_report_from(std::ostreambuf_iterator{ss}, expReport);
427
428 std::ranges::sort(
429 matchResults,
430 {},
431 [](expectation::MatchResult const& m) -> auto const& {
432 return std::visit([](auto const& inner) -> auto const& { return inner.description; }, m);
433 });
434 std::ranges::subrange const violations = std::ranges::stable_partition(
435 matchResults,
436 [](expectation::MatchResult const& r) { return std::holds_alternative<expectation::MatchSuccess>(r); });
437
438 MIMICPP_ASSERT(!violations.empty(), "Zero violations do not denote a no-match.");
439 detail::stringify_expectation_report_requirement_violations(std::ostreambuf_iterator{ss}, violations, "\t");
440 detail::stringify_expectation_report_requirement_adherences(
441 std::ostreambuf_iterator{ss},
442 std::span{matchResults.data(), violations.data()},
443 "\t");
444 }
445 }
446
447 detail::stringify_stacktrace(
448 std::ostreambuf_iterator{ss},
449 call.stacktrace);
450
451 return std::move(ss).str();
452 }
453
454 [[nodiscard]]
456 {
457 StringStreamT ss{};
458
459 ss << "Unfulfilled ";
460 detail::stringify_expectation_report_from(std::ostreambuf_iterator{ss}, expectationReport);
461 detail::stringify_expectation_report_target(std::ostreambuf_iterator{ss}, expectationReport);
462 ss << "\tBecause ";
463 std::visit(
464 std::bind_front(detail::unfulfilled_reason_printer{}, std::ostreambuf_iterator{ss}),
465 expectationReport.controlReport);
466
467 return std::move(ss).str();
468 }
469
470 [[nodiscard]]
472 CallReport const& call,
473 ExpectationReport const& expectationReport,
474 std::exception_ptr const& exception)
475 {
476 StringStreamT ss{};
477 ss << "Unhandled Exception ";
478
479 try
480 {
481 std::rethrow_exception(exception);
482 }
483 catch (const std::exception& e)
484 {
485 ss << "with message `" << e.what() << "`\n";
486 }
487 catch (...)
488 {
489 ss << "of unknown type.\n";
490 }
491
492 ss << "\t" << "While checking ";
493 detail::stringify_expectation_report_from(std::ostreambuf_iterator{ss}, expectationReport);
494
495 ss << "For ";
496 detail::stringify_call_report_from(std::ostreambuf_iterator{ss}, call);
497 detail::stringify_call_report_target(std::ostreambuf_iterator{ss}, call);
498 detail::stringify_call_report_arguments(std::ostreambuf_iterator{ss}, call, "\t");
499
500 detail::stringify_stacktrace(
501 std::ostreambuf_iterator{ss},
502 call.stacktrace);
503
504 return std::move(ss).str();
505 }
506}
507
508#endif
#define MIMICPP_ASSERT(condition, msg)
Definition Config.hpp:54
#define MIMICPP_DETAIL_MODULE_EXPORT
Definition Config.hpp:19
Contains the extracted info from a typed call::Info.
Definition CallReport.hpp:43
Contains the extracted info from a typed expectation.
Definition ExpectationReport.hpp:85
control_state_t controlReport
Definition ExpectationReport.hpp:89
Contains the expectation-report and the match-results to a specific call.
Definition MatchReport.hpp:22
ExpectationReport expectationReport
Definition MatchReport.hpp:24
expectation::MatchResults matchResults
Definition MatchReport.hpp:25
constexpr TypeMatcher< T > type
Matcher, which can be used as a last resort to disambiguate similar overloads.
Definition GeneralMatchers.hpp:480
constexpr printing::PrintFn print
Functional object, converting the given object to its textual representation.
Definition Print.hpp:183
Definition Call.hpp:24
std::variant< MatchSuccess, MatchFailure > MatchResult
Definition Common.hpp:41
OutIter format_to(OutIter out, format_string< Args... > const fmt, Args &&... args)
Definition Format.hpp:175
Definition BasicReporter.hpp:26
StringT stringify_full_match(CallReport const &call, MatchReport match)
Definition StringifyReports.hpp:317
StringT stringify_unhandled_exception(CallReport const &call, ExpectationReport const &expectationReport, std::exception_ptr const &exception)
Definition StringifyReports.hpp:471
StringT stringify_unfulfilled_expectation(ExpectationReport const &expectationReport)
Definition StringifyReports.hpp:455
StringT stringify_inapplicable_matches(CallReport const &call, std::span< MatchReport > reports)
Definition StringifyReports.hpp:354
StringT stringify_no_matches(CallReport const &call, std::span< MatchReport > noMatchReports)
Definition StringifyReports.hpp:394
void unreachable()
Invokes undefined behavior.
Definition C++23Backports.hpp:40
std::basic_ostringstream< CharT, CharTraitsT > StringStreamT
Definition Format.hpp:38
std::basic_string_view< CharT, CharTraitsT > StringViewT
Definition Fwd.hpp:398
std::basic_string< CharT, CharTraitsT > StringT
Definition Fwd.hpp:397