mimic++ v9.2.1
Loading...
Searching...
No Matches
StringifyReports.hpp
Go to the documentation of this file.
1// Copyright Dominic (DNKpp) Koepke 2024 - 2025.
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 {
91 out = format::format_to(
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<std::optional<StringT> const> const descriptions,
126 StringViewT const linePrefix)
127 {
128 if (descriptions.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& description : descriptions)
137 {
138 if (description)
139 {
140 out = std::ranges::copy(linePrefix, std::move(out)).out;
141 out = format::format_to(std::move(out), " + ");
142 out = std::ranges::copy(*description, std::move(out)).out;
143 out = format::format_to(std::move(out), "\n");
144 }
145 }
146
147 return out;
148 }
149
150 struct inapplicable_reason_printer
151 {
152 template <print_iterator OutIter>
153 OutIter operator()([[maybe_unused]] OutIter out, [[maybe_unused]] state_applicable const& state) const
154 {
156 }
157
158 template <print_iterator OutIter>
159 constexpr OutIter operator()(OutIter out, state_inapplicable const& state) const
160 {
161 auto const totalSequences = std::ranges::ssize(state.sequences)
162 + std::ranges::ssize(state.inapplicableSequences);
163 out = format::format_to(
164 std::move(out),
165 "it's not Head of {} Sequence(s) ({} total).\n",
166 std::ranges::ssize(state.inapplicableSequences),
167 totalSequences);
168
169 MIMICPP_ASSERT(!state.inapplicableSequences.empty(), "Inapplicable sequences can not be empty.");
170 for (auto const& sequence : state.inapplicableSequences)
171 {
172 MIMICPP_ASSERT(sequence.headFrom, "Head-from can not be empty.");
173 out = format::format_to(std::move(out), "\t\tExpectation defined at ");
174 out = mimicpp::print(std::move(out), *sequence.headFrom);
175 out = format::format_to(std::move(out), " is Head of Sequence from ");
176 out = mimicpp::print(std::move(out), sequence.from);
177 out = format::format_to(std::move(out), "\n");
178 }
179
180 return out;
181 }
182
183 template <print_iterator OutIter>
184 constexpr OutIter operator()(OutIter out, state_saturated const& state) const
185 {
186 out = format::format_to(
187 std::move(out),
188 "it's already saturated (matched {} out of {} times).\n",
189 state.count,
190 state.max);
191
192 return out;
193 }
194 };
195
196 template <print_iterator OutIter>
197 OutIter stringify_expectation_report_requirement_violations(
198 OutIter out,
199 std::span<std::optional<StringT> const> const descriptions,
200 StringViewT const linePrefix)
201 {
202 MIMICPP_ASSERT(!descriptions.empty(), "Zero requirements can never be violated.");
203
204 out = std::ranges::copy(linePrefix, std::move(out)).out;
205 out = format::format_to(std::move(out), "Due to Violation(s):\n");
206
207 int withoutDescription{0};
208 for (auto const& description : descriptions)
209 {
210 if (description)
211 {
212 out = std::ranges::copy(linePrefix, std::move(out)).out;
213 out = format::format_to(std::move(out), " - ");
214 out = std::ranges::copy(*description, std::move(out)).out;
215 out = format::format_to(std::move(out), "\n");
216 }
217 else
218 {
219 ++withoutDescription;
220 }
221 }
222
223 if (0 < withoutDescription)
224 {
225 out = std::ranges::copy(linePrefix, std::move(out)).out;
226 out = format::format_to(std::move(out), " - ");
227 out = format::format_to(
228 std::move(out),
229 "{} Requirement(s) failed without further description.\n",
230 withoutDescription);
231 }
232
233 return out;
234 }
235
236 struct unfulfilled_reason_printer
237 {
238 template <print_iterator OutIter>
239 OutIter operator()(OutIter out, state_applicable const& state) const
240 {
241 return stringify_times_state(
242 std::move(out),
243 state.count,
244 state.min,
245 state.max);
246 }
247
248 template <print_iterator OutIter>
249 OutIter operator()(OutIter out, state_inapplicable const& state) const
250 {
251 return stringify_times_state(
252 std::move(out),
253 state.count,
254 state.min,
255 state.max);
256 }
257
258 template <print_iterator OutIter>
259 OutIter operator()([[maybe_unused]] OutIter out, [[maybe_unused]] state_saturated const& state) const
260 {
262 }
263
264 private:
265 template <print_iterator OutIter>
266 static OutIter stringify_times_state(OutIter out, int const current, int const min, int const max)
267 {
268 const auto verbalizeValue = [](OutIter o, int const value) {
269 MIMICPP_ASSERT(0 < value, "Invalid value.");
270
271 switch (value)
272 {
273 case 1: return format::format_to(std::move(o), "once");
274 case 2: return format::format_to(std::move(o), "twice");
275 default: return format::format_to(std::move(o), "{} times", value);
276 }
277 };
278
279 MIMICPP_ASSERT(current < min, "State doesn't denote an unsatisfied state.");
280
281 out = format::format_to(std::move(out), "matching ");
282
283 if (min == max)
284 {
285 out = format::format_to(std::move(out), "exactly ");
286 out = verbalizeValue(std::move(out), min);
287 out = format::format_to(std::move(out), " ");
288 }
289 else if (max == std::numeric_limits<int>::max())
290 {
291 out = format::format_to(std::move(out), "at least ");
292 out = verbalizeValue(std::move(out), min);
293 out = format::format_to(std::move(out), " ");
294 }
295 else
296 {
297 out = format::format_to(
298 std::move(out),
299 "between {} and {} times ",
300 min,
301 max);
302 }
303
304 return format::format_to(
305 std::move(out),
306 "was expected => requires {} further match(es).\n",
307 min - current);
308 }
309 };
310}
311
313{
314 [[nodiscard]]
316 {
317 MIMICPP_ASSERT(std::holds_alternative<state_applicable>(expectation.controlReport), "Report denotes inapplicable expectation.");
318
319 StringStreamT ss{};
320 ss << "Matched ";
321 detail::stringify_call_report_from(std::ostreambuf_iterator{ss}, call);
322 detail::stringify_call_report_target(std::ostreambuf_iterator{ss}, call);
323 detail::stringify_call_report_arguments(std::ostreambuf_iterator{ss}, call, "\t");
324
325 ss << "\t" << "Chose ";
326 detail::stringify_expectation_report_from(std::ostreambuf_iterator{ss}, expectation);
327
328 if (!expectation.requirementDescriptions.empty())
329 {
330 std::ranges::sort(expectation.requirementDescriptions);
331 detail::stringify_expectation_report_requirement_adherences(
332 std::ostreambuf_iterator{ss},
333 expectation.requirementDescriptions,
334 "\t");
335 }
336 else
337 {
338 ss << "\t" << "Without any Requirements.\n";
339 }
340
341 detail::stringify_stacktrace(
342 std::ostreambuf_iterator{ss},
343 call.stacktrace);
344
345 return std::move(ss).str();
346 }
347
348 [[nodiscard]]
349 inline StringT stringify_inapplicable_matches(CallReport const& call, std::span<ExpectationReport> expectations)
350 {
351 MIMICPP_ASSERT(!expectations.empty(), "No expectations given.");
352
353 StringStreamT ss{};
354
355 ss << "Unmatched ";
356 detail::stringify_call_report_from(std::ostreambuf_iterator{ss}, call);
357 detail::stringify_call_report_target(std::ostreambuf_iterator{ss}, call);
358 detail::stringify_call_report_arguments(std::ostreambuf_iterator{ss}, call, "\t");
359
360 ss << expectations.size() << " inapplicable but otherwise matching Expectation(s):";
361
362 for (int i{};
363 auto& expReport : expectations)
364 {
365 ss << "\n\t#" << ++i << " ";
366 detail::stringify_expectation_report_from(std::ostreambuf_iterator{ss}, expReport);
367
368 ss << "\tBecause ";
369 std::visit(
370 std::bind_front(detail::inapplicable_reason_printer{}, std::ostreambuf_iterator{ss}),
371 expReport.controlReport);
372
373 std::ranges::sort(expReport.requirementDescriptions);
374 detail::stringify_expectation_report_requirement_adherences(
375 std::ostreambuf_iterator{ss},
376 expReport.requirementDescriptions,
377 "\t");
378 }
379
380 detail::stringify_stacktrace(
381 std::ostreambuf_iterator{ss},
382 call.stacktrace);
383
384 return std::move(ss).str();
385 }
386
387 [[nodiscard]]
388 inline StringT stringify_no_matches(CallReport const& call, std::span<NoMatchReport> noMatchReports)
389 {
390 StringStreamT ss{};
391
392 ss << "Unmatched ";
393 detail::stringify_call_report_from(std::ostreambuf_iterator{ss}, call);
394 detail::stringify_call_report_target(std::ostreambuf_iterator{ss}, call);
395 detail::stringify_call_report_arguments(std::ostreambuf_iterator{ss}, call, "\t");
396
397 std::vector<NoMatchReport*> applicableReports{};
398 for (auto& noMatch : noMatchReports)
399 {
400 if (std::holds_alternative<state_applicable>(noMatch.expectationReport.controlReport))
401 {
402 applicableReports.emplace_back(&noMatch);
403 }
404 }
405
406 if (applicableReports.empty())
407 {
408 ss << "No applicable Expectations available!\n";
409 }
410 else
411 {
412 ss << applicableReports.size() << " applicable non-matching Expectation(s):";
413
414 for (int i{};
415 auto* report : applicableReports)
416 {
417 auto& [expReport, outcomes] = *report;
418
419 ss << "\n\t#" << ++i << " ";
420 detail::stringify_expectation_report_from(std::ostreambuf_iterator{ss}, expReport);
421
422 std::span const violations = util::partition_by(
423 expReport.requirementDescriptions,
424 outcomes.outcomes,
425 std::bind_front(std::equal_to{}, true));
426 MIMICPP_ASSERT(!violations.empty(), "Zero violations do not denote a no-match.");
427 std::ranges::sort(violations);
428 detail::stringify_expectation_report_requirement_violations(
429 std::ostreambuf_iterator{ss},
430 violations,
431 "\t");
432
433 std::span const adherences{expReport.requirementDescriptions.data(), violations.data()};
434 std::ranges::sort(adherences);
435 detail::stringify_expectation_report_requirement_adherences(
436 std::ostreambuf_iterator{ss},
437 adherences,
438 "\t");
439 }
440 }
441
442 detail::stringify_stacktrace(
443 std::ostreambuf_iterator{ss},
444 call.stacktrace);
445
446 return std::move(ss).str();
447 }
448
449 [[nodiscard]]
451 {
452 StringStreamT ss{};
453
454 ss << "Unfulfilled ";
455 detail::stringify_expectation_report_from(std::ostreambuf_iterator{ss}, expectationReport);
456 detail::stringify_expectation_report_target(std::ostreambuf_iterator{ss}, expectationReport);
457 ss << "\tBecause ";
458 std::visit(
459 std::bind_front(detail::unfulfilled_reason_printer{}, std::ostreambuf_iterator{ss}),
460 expectationReport.controlReport);
461
462 return std::move(ss).str();
463 }
464
465 [[nodiscard]]
467 CallReport const& call,
468 ExpectationReport const& expectationReport,
469 std::exception_ptr const& exception)
470 {
471 StringStreamT ss{};
472 ss << "Unhandled Exception ";
473
474 try
475 {
476 std::rethrow_exception(exception);
477 }
478 catch (const std::exception& e)
479 {
480 ss << "with message `" << e.what() << "`\n";
481 }
482 catch (...)
483 {
484 ss << "of unknown type.\n";
485 }
486
487 ss << "\t" << "While checking ";
488 detail::stringify_expectation_report_from(std::ostreambuf_iterator{ss}, expectationReport);
489
490 ss << "For ";
491 detail::stringify_call_report_from(std::ostreambuf_iterator{ss}, call);
492 detail::stringify_call_report_target(std::ostreambuf_iterator{ss}, call);
493 detail::stringify_call_report_arguments(std::ostreambuf_iterator{ss}, call, "\t");
494
495 detail::stringify_stacktrace(
496 std::ostreambuf_iterator{ss},
497 call.stacktrace);
498
499 return std::move(ss).str();
500 }
501}
502
503#endif
#define MIMICPP_ASSERT(condition, msg)
Definition Config.hpp:51
#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
std::vector< std::optional< StringT > > requirementDescriptions
Definition ExpectationReport.hpp:91
control_state_t controlReport
Definition ExpectationReport.hpp:89
constexpr TypeMatcher< T > type
Matcher, which can be used as a last resort to disambiguate similar overloads.
Definition GeneralMatchers.hpp:297
constexpr printing::PrintFn print
Functional object, converting the given object to its textual representation.
Definition Print.hpp:183
Definition Call.hpp:24
Definition BasicReporter.hpp:27
StringT stringify_unhandled_exception(CallReport const &call, ExpectationReport const &expectationReport, std::exception_ptr const &exception)
Definition StringifyReports.hpp:466
StringT stringify_unfulfilled_expectation(ExpectationReport const &expectationReport)
Definition StringifyReports.hpp:450
StringT stringify_inapplicable_matches(CallReport const &call, std::span< ExpectationReport > expectations)
Definition StringifyReports.hpp:349
StringT stringify_no_matches(CallReport const &call, std::span< NoMatchReport > noMatchReports)
Definition StringifyReports.hpp:388
StringT stringify_full_match(CallReport const &call, ExpectationReport expectation)
Definition StringifyReports.hpp:315
constexpr std::ranges::borrowed_subrange_t< Target > partition_by(Target &&targetRange, Control &&controlRange, Predicate predicate, Projection projection={})
Partitions the target range by the predicate results on the control range.
Definition Algorithm.hpp:62
void unreachable()
Invokes undefined behavior.
Definition C++23Backports.hpp:40
std::basic_ostringstream< CharT, CharTraitsT > StringStreamT
Definition Format.hpp:35
std::basic_string_view< CharT, CharTraitsT > StringViewT
Definition Fwd.hpp:392
std::basic_string< CharT, CharTraitsT > StringT
Definition Fwd.hpp:391