Sequoia
Loading...
Searching...
No Matches
SemanticsCheckersDetails.hpp
Go to the documentation of this file.
1
2// Copyright Oliver J. Rosten 2020. //
3// Distributed under the GNU GENERAL PUBLIC LICENSE, Version 3.0. //
4// (See accompanying file LICENSE.md or copy at //
5// https://www.gnu.org/licenses/gpl-3.0.en.html) //
7
8#pragma once
9
66
68
69#include <compare>
70#include <format>
71#include <optional>
72
73namespace sequoia::testing
74{
75 enum class comparison_flavour { equality, inequality, less_than, greater_than, leq, geq, threeway };
76
77 template<comparison_flavour C>
78 using comparison_constant = std::integral_constant<comparison_flavour, C>;
79
80 using equality_type = comparison_constant<comparison_flavour::equality>;
81 using inequality_type = comparison_constant<comparison_flavour::inequality>;
82 using less_than_type = comparison_constant<comparison_flavour::less_than>;
83 using greater_than_type = comparison_constant<comparison_flavour::greater_than>;
84 using leq_type = comparison_constant<comparison_flavour::leq>;
85 using geq_type = comparison_constant<comparison_flavour::geq>;
86 using threeway_type = comparison_constant<comparison_flavour::threeway>;
87
88 [[nodiscard]]
89 std::string to_string(comparison_flavour f);
90
91 template<class T>
92 using optional_ref = std::optional<std::reference_wrapper<T>>;
93
94 template<test_mode Mode, std::equality_comparable T, class U>
95 inline constexpr bool checkable_against_for_semantics{
96 checkable_against<with_best_available_check_t<minimal_reporting_permitted::yes>, Mode, T, U, tutor<null_advisor>>
97 };
98
99 template<test_mode Mode, std::equality_comparable T, class U>
100 inline constexpr bool equivalence_checkable_for_semantics{
101 (!std::is_same_v<std::remove_cvref_t<T>, std::remove_cvref_t<U>>)
102 && checkable_against<with_best_available_check_t<minimal_reporting_permitted::no>, Mode, T, U, tutor<null_advisor>>
103 };
104}
105
107{
108 template<class...> struct do_swap;
109
110 template<> struct do_swap<>
111 {
112 constexpr static bool value{true};
113 };
114
116 {
117 template<class T> constexpr void operator()(const T&) const noexcept {}
118 };
119
120 template<class T>
122
123 template<std::equality_comparable T>
125 {
126 protected:
127 ~auxiliary_data_policy() = default;
128 };
129
130 template<std::totally_ordered T>
132 {
133 constexpr explicit auxiliary_data_policy(std::weak_ordering order)
134 : m_Order{order}
135 {}
136
137 [[nodiscard]]
138 std::weak_ordering order() const noexcept
139 {
140 return m_Order;
141 }
142 protected:
143 ~auxiliary_data_policy() = default;
144 private:
145 std::weak_ordering m_Order;
146 };
147
148 template<class T>
150 {
151 using auxiliary_data_policy<T>::auxiliary_data_policy;
152 };
153
154 template<class Actions, class... Args>
155 inline constexpr bool has_post_comparison_action
156 = requires (std::remove_cvref_t<Actions> actions, std::remove_cvref_t<Args>&... args) {
157 actions.post_comparison_action(args...);
158 };
159
160 template<class Actions, class... Args>
161 inline constexpr bool has_post_move_action
162 = requires (std::remove_cvref_t<Actions> actions, std::remove_cvref_t<Args>&... args) {
163 actions.post_move_action(args...);
164 };
165
166 template<class Actions, class... Args>
167 inline constexpr bool has_post_move_assign_action
168 = requires (std::remove_cvref_t<Actions> actions, std::remove_cvref_t<Args>&... args) {
169 actions.post_move_assign_action(args...);
170 };
171
172 template<class Actions, class... Args>
173 inline constexpr bool has_post_swap_action
174 = requires (std::remove_cvref_t<Actions> actions, std::remove_cvref_t<Args>&... args) {
175 actions.post_swap_action(args...);
176 };
177
178 template<class Actions, class... Args>
179 inline constexpr bool has_post_serialization_action
180 = requires (std::remove_cvref_t<Actions> actions, std::remove_cvref_t<Args>&... args) {
181 actions.post_serialization_action(args...);
182 };
183
184 template<test_mode Mode, class T, class U>
185 requires equivalence_checkable_for_semantics<Mode, T, U>
186 void check_best_equivalence(test_logger<Mode>& logger, const T& x, const T& y, const U& xEquivalent, const U& yEquivalent)
187 {
188 using availability_t = with_best_available_check_t<minimal_reporting_permitted::no>;
189 check(availability_t{}, "x not equivalent to xEquivalent", logger, x, xEquivalent);
190 check(availability_t{}, "y not equivalent to yEquivalent", logger, y, yEquivalent);
191 }
192
193 //================================ comparisons ================================//
194
195 template<test_mode Mode, class Actions, pseudoregular T, class... Args>
196 [[nodiscard]]
197 static bool check_prerequisites(test_logger<Mode>& logger, const Actions& actions, const T& x, const T& y, const Args&... args)
198 {
199 return check_equality_prerequisites(logger, actions, x, y, args...);
200 }
201
202 template<test_mode Mode, class Actions, pseudoregular T, class... Args>
203 requires std::totally_ordered<T>
204 [[nodiscard]]
205 static bool check_prerequisites(test_logger<Mode>& logger, const Actions& actions, const T& x, const T& y, const Args&... args)
206 {
207 return check_orderable_prerequisites(logger, actions, x, y, args...);
208 }
209
210 template<test_mode Mode, class Actions, moveonly T, class U, class... Args>
211 [[nodiscard]]
212 static bool check_prerequisites(test_logger<Mode>& logger, const Actions& actions, const T& x, const T& y, const U& xEquivalent, const U& yEquivalent, const Args&... args)
213 {
214 return check_equality_prerequisites(logger, actions, x, y, xEquivalent, yEquivalent, args...);
215 }
216
217 template<test_mode Mode, class Actions, moveonly T, class U, class... Args>
218 requires std::totally_ordered<T>
219 [[nodiscard]]
220 static bool check_prerequisites(test_logger<Mode>& logger, const Actions& actions, const T& x, const T& y, const U& xEquivalent, const U& yEquivalent, const Args&... args)
221 {
222 return check_orderable_prerequisites(logger, actions, x, y, xEquivalent, yEquivalent, args...);
223 }
224
225 template<test_mode Mode, class Actions, moveonly T, class... Args>
226 [[nodiscard]]
227 static bool check_prerequisites(test_logger<Mode>& logger, const Actions& actions, const T& x, const T& y, const Args&... args)
228 {
229 return check_equality_prerequisites(logger, actions, x, y, args...);
230 }
231
232 template<test_mode Mode, class Actions, moveonly T, class... Args>
233 requires std::totally_ordered<T>
234 [[nodiscard]]
235 static bool check_prerequisites(test_logger<Mode>& logger, const Actions& actions, const T& x, const T& y, const Args&... args)
236 {
237 return check_orderable_prerequisites(logger, actions, x, y, args...);
238 }
239
240 //================================ comparisons ================================//
241
242 template<test_mode Mode, comparison_flavour C, class Actions, movable_comparable T, invocable_r<bool, T> Fn, class... Args>
243 bool do_check_comparison_consistency(test_logger<Mode>& logger, comparison_constant<C> comparison, [[maybe_unused]] const Actions& actions, const T& x, std::string_view tag, Fn fn, [[maybe_unused]] const Args&... args)
244 {
245 if(!check(std::string{"operator"}.append(to_string(comparison.value)).append(" is inconsistent ").append(tag), logger, fn(x)))
246 return false;
247
248 if constexpr (has_post_comparison_action<Actions, test_logger<Mode>, comparison_constant<C>, T, std::string_view, Args...>)
249 {
250 if(!actions.post_comparison_action(logger, comparison, x, tag, args...))
251 return false;
252 }
253
254 return true;
255 }
256
257 template<test_mode Mode, comparison_flavour C, class Actions, movable_comparable T, invocable_r<bool, T> Fn>
258 bool check_comparison_consistency(test_logger<Mode>& logger, comparison_constant<C> comparison, const Actions& actions, const T& x, const T& y, Fn fn)
259 {
260 sentinel sentry{logger, ""};
261
262 do_check_comparison_consistency(logger, comparison, actions, x, "(x)", fn);
263 do_check_comparison_consistency(logger, comparison, actions, y, "(y)", fn);
264
265 return !sentry.failure_detected();
266 }
267
268 template<test_mode Mode, class Actions, std::totally_ordered T, class... Args>
269 [[nodiscard]]
270 bool check_ordering_operators(test_logger<Mode>& logger, const Actions& actions, const T& x, const T& y, const Args&... args)
271 {
272 sentinel sentry{logger, ""};
273
274 check_comparison_consistency(logger, less_than_type{}, actions, x, y, [](const T& x) { return !(x < x); }, args...);
275 check_comparison_consistency(logger, leq_type{}, actions, x, y, [](const T& x) { return x <= x; }, args...);
276 check_comparison_consistency(logger, greater_than_type{}, actions, x, y, [](const T& x) { return !(x > x); }, args...);
277 check_comparison_consistency(logger, geq_type{}, actions, x, y, [](const T& x) { return x >= x; }, args...);
278
279 if constexpr (std::three_way_comparable<T>)
280 {
281 check_comparison_consistency(logger, threeway_type{}, actions, x, y, [](const T& x) { return (x <=> x) == 0; }, args...);
282 }
283
284 return !sentry.failure_detected();
285 }
286
287 template<test_mode Mode, class Actions, std::totally_ordered T, class... Args>
288 [[nodiscard]]
289 bool check_ordering_consistency(test_logger<Mode>& logger, const Actions& actions, const T& x, const T& y, const Args&... args)
290 {
291 if(!check_ordering_operators(logger, actions, x, y, args...)) return false;
292
293 auto comp{
294 [&logger](const T& x, const T& y){
295 sentinel sentry{logger, ""};
296
297 check("operator> and operator< are inconsistent", logger, y > x);
298 check("operator< and operator<= are inconsistent", logger, x <= y);
299 check("operator< and operator>= are inconsistent", logger, y >= x);
300
301 if constexpr (std::three_way_comparable<T>)
302 {
303 check("operator< and operator<=> are inconsistent", logger, (x <=> y) < 0);
304 }
305
306 return !sentry.failure_detected();
307 }
308 };
309
310 return x < y ? comp(x,y) : comp(y,x);
311 }
312
313 template<test_mode Mode, class Actions, std::totally_ordered T, class U, class... Args>
314 [[nodiscard]]
315 bool check_ordering_consistency(test_logger<Mode>& logger, const Actions& actions, const T& x, const T& y, const U&, const U&, const Args&... args)
316 {
317 return check_ordering_consistency(logger, actions, x, y, args...);
318 }
319
320 template<test_mode Mode, class Actions, std::equality_comparable T, class... Args>
321 [[nodiscard]]
322 bool check_equality_prerequisites(test_logger<Mode>& logger, const Actions& actions, const T& x, const T& y, const Args&... args)
323 {
324 const bool eq{check_comparison_consistency(logger, equality_type{}, actions, x, y, [](const T& x) { return x == x; }, args...)};
325 const bool neq{check_comparison_consistency(logger, inequality_type{}, actions, x, y, [](const T& x) { return !(x != x); }, args...)};
326
327 return eq && neq && check("Prerequisite - for checking semantics, x and y are assumed to be different", logger, x != y);
328 }
329
330 template<test_mode Mode, std::equality_comparable T, class U>
331 requires checkable_against_for_semantics<Mode, T, U>
332 bool check_against(std::string message, test_logger<Mode>& logger, const T& x, const U& xEquivalent)
333 {
334 return check(with_best_available_check_t<minimal_reporting_permitted::yes>{},
335 std::move(message),
336 logger,
337 x,
338 xEquivalent);
339 }
340
341 template<test_mode Mode, class Actions, std::equality_comparable T, class U, class... Args>
342 [[nodiscard]]
343 bool check_equality_prerequisites(test_logger<Mode>& logger, const Actions& actions, const T& x, const T& y, const U& xEquivalent, const U& yEquivalent, const Args&... args)
344 {
345 if(!check_equality_prerequisites(logger, actions, x, y, args...))
346 return false;
347
348 auto makeMessage{
349 [](std::string_view var) {
350 return std::format("Prerequisite: {0} and {0}Equivalent should be equivalent", var);
351 }
352 };
353
354 const bool xPassed{check_against(makeMessage("x"), logger, x, xEquivalent)},
355 yPassed{check_against(makeMessage("y"), logger, y, yEquivalent)};
356
357 return xPassed && yPassed;
358 }
359
360 template<test_mode Mode, class Actions, std::totally_ordered T, class... Args>
361 [[nodiscard]]
362 bool check_orderable_prerequisites(test_logger<Mode>& logger, const Actions& actions, const T& x, const T& y, const Args&... args)
363 {
364 if(check_equality_prerequisites(logger, actions, x, y, args...))
365 {
366 const auto order{actions.order()};
367 if(check("Prerequisite - for checking semantics, order must be weak_ordering::less or weak_ordering::greater",
368 logger, order != 0))
369 {
370 if(check_ordering_consistency(logger, actions, x, y, args...))
371 {
372 const bool cond{order < 0 ? x < y : x > y};
373 auto mess{
374 [order](){
375 std::string mess{"Prerequisite - for ordered semantics, it is assumed that "};
376 return order == 0 ? mess.append("x < y") : mess.append("y > x");
377 }
378 };
379
380 if constexpr(serializable<T>)
381 {
382 return check(mess(), logger, cond,
383 tutor{[](const T& x, const T& y) {
384 return prediction_message(to_string(x), to_string(y)); } });
385 }
386 else
387 {
388 return check(mess(), logger, cond);
389 }
390 }
391 }
392 }
393
394 return false;
395 }
396
397 //================================ move construction ================================ //
398
399 template<test_mode Mode, class Actions, movable_comparable T, class U, class V, class... Args>
400 requires checkable_against_for_semantics<Mode, T, U> && checkable_against_for_semantics<Mode, T, V>
401 std::optional<T> do_check_move_construction(test_logger<Mode>& logger,
402 [[maybe_unused]] const Actions& actions,
403 T&& z,
404 const U& y,
405 optional_ref<const V> movedFrom,
406 const Args&... args)
407 {
408 T w{std::move(z)};
409 if(!check_against("Inconsistent move construction", logger, w, y))
410 return {};
411
412 if(movedFrom.has_value())
413 {
414 check_against("Incorrect moved-from value after move construction", logger, z, movedFrom.value().get());
415 }
416
417 if constexpr(has_post_move_action<Actions, test_logger<Mode>, T, Args...>)
418 {
419 actions.post_move_action(logger, w, args...);
420 }
421
422 return w;
423 }
424
425 template<test_mode Mode, class Actions, movable_comparable T, class U, class V>
426 requires checkable_against_for_semantics<Mode, T, U>
427 std::optional<T> check_move_construction(test_logger<Mode>& logger,
428 const Actions& actions,
429 T&& z,
430 const U& y,
431 optional_ref<const V> movedFrom)
432 {
433 return do_check_move_construction(logger, actions, std::forward<T>(z), y, movedFrom);
434 }
435
436 //================================ move assign ================================//
437
438 template<test_mode Mode, class Actions, movable_comparable T, class U, class V, std::invocable<T&> Mutator, class... Args>
439 requires checkable_against_for_semantics<Mode, T, U> && checkable_against_for_semantics<Mode, T, V>
440 void do_check_move_assign(test_logger<Mode>& logger,
441 [[maybe_unused]] const Actions& actions,
442 T& z,
443 T&& y,
444 const U& yEquivalent,
445 optional_ref<const V> movedFrom,
446 [[maybe_unused]] Mutator&& yMutator,
447 const Args&... args)
448 {
449 z = std::move(y);
450 if(!check_against("Inconsistent move assignment (from y)", logger, z, yEquivalent))
451 return;
452
453 if(movedFrom.has_value())
454 {
455 check_against("Incorrect moved-from value after move assignment", logger, y, movedFrom.value().get());
456 }
457
458 if constexpr(has_post_move_assign_action<Actions, test_logger<Mode>, T, Mutator, Args...>)
459 {
460 actions.post_move_assign_action(logger, z, std::move(yMutator), args...);
461 }
462 }
463
464 template<test_mode Mode, class Actions, movable_comparable T, class U, class V, std::invocable<T&> Mutator>
465 requires checkable_against_for_semantics<Mode, T, U> && checkable_against_for_semantics<Mode, T, V>
466 void check_move_assign(test_logger<Mode>& logger,
467 const Actions& actions,
468 T& z,
469 T&& y,
470 const U& yEquivalent,
471 optional_ref<const V> movedFrom,
472 Mutator m)
473 {
474 do_check_move_assign(logger, actions, z, std::forward<T>(y), yEquivalent, movedFrom, std::move(m));
475 }
476
477 //================================ swap ================================//
478
479 template<test_mode Mode, class Actions, movable_comparable T, class U, class... Args>
480 requires checkable_against_for_semantics<Mode, T, U>
481 bool do_check_swap(test_logger<Mode>& logger,
482 [[maybe_unused]] const Actions& actions,
483 T&& x,
484 T&& y,
485 const U& xEquivalent,
486 const U& yEquivalent,
487 [[maybe_unused]] const Args&... args)
488 {
489 std::ranges::swap(x, y);
490
491 const bool swapy{
492 check_against("Inconsistent Swap (y)", logger, y, xEquivalent)
493 };
494
495 const bool swapx{
496 check_against("Inconsistent Swap (x)", logger, x, yEquivalent)
497 };
498
499 if(swapx && swapy)
500 {
501 if constexpr(has_post_swap_action<Actions, test_logger<Mode>, T, T, T, Args...>)
502 {
503 actions.post_swap_action(logger, x, y, yEquivalent, args...);
504 }
505
506 std::ranges::swap(y,y);
507 return check_against("Inconsistent Self Swap", logger, y, xEquivalent);
508 }
509
510 return false;
511 }
512
513 template<test_mode Mode, class Actions, movable_comparable T, class U>
514 requires checkable_against_for_semantics<Mode, T, U>
515 bool check_swap(test_logger<Mode>& logger, const Actions& actions, T&& x, T&& y, const U& xEquivalent, const U& yEquivalent)
516 {
517 return do_check_swap(logger, actions, std::move(x), std::move(y), xEquivalent, yEquivalent);
518 }
519
520 template<test_mode Mode, class Actions, pseudoregular T, class U, std::invocable<T&> Mutator>
521 requires checkable_against_for_semantics<Mode, T, U>
522 bool check_swap(test_logger<Mode>& logger, const Actions& actions, T&& x, T&& y, const U& xEquivalent, const U& yEquivalent, Mutator yMutator)
523 {
524 return do_check_swap(logger, actions, std::move(x), std::move(y), xEquivalent, yEquivalent, std::move(yMutator));
525 }
526
527 //================================ serialization ================================ //
528
529 template<test_mode Mode, class Actions, movable_comparable T, class... Args>
530 requires (serializable_to<T, std::stringstream> && deserializable_from<T, std::stringstream>)
531 bool do_check_serialization(test_logger<Mode>& logger,
532 [[maybe_unused]] const Actions& actions,
533 T&& u,
534 const T& y,
535 [[maybe_unused]] const Args&... args)
536 {
537 std::stringstream s{};
538 s << y;
539
540 if constexpr(has_post_serialization_action<Actions, test_logger<Mode>, T, Args...>)
541 {
542 actions.post_serialization_action(logger, y, args...);
543 }
544
545 s >> u;
546
547 return check_against("Inconsistent (de)serialization", logger, u, y);
548 }
549
550 template<test_mode Mode, class Actions, movable_comparable T>
551 requires (serializable_to<T, std::stringstream> && deserializable_from<T, std::stringstream>)
552 bool check_serialization(test_logger<Mode>& logger, const Actions& actions, T&& u, const T& y)
553 {
554 return do_check_serialization(logger, actions, std::forward<T>(u), y);
555 }
556}
Utilities for the advice framework, which provides hints for certain failures.
Concepts which are sufficiently general to appear in the sequoia namespace.
Free functions for performing checks, together with the 'checker' class template which wraps them.
bool check(CheckType flavour, std::string description, test_logger< Mode > &logger, Iter first, Sentinel last, PredictionIter predictionFirst, PredictionSentinel predictionLast, tutor< Advisor > advisor={})
The workhorse for comparing the contents of ranges.
Definition: FreeCheckers.hpp:377
Utilities for recording the outcome of tests.
Condition for applying a container check.
Definition: AllocationCheckersDetails.cpp:15
Definition: SemanticsCheckersDetails.hpp:121
Definition: SemanticsCheckersDetails.hpp:150
Definition: SemanticsCheckersDetails.hpp:111
Definition: SemanticsCheckersDetails.hpp:108
Definition: SemanticsCheckersDetails.hpp:116