65namespace sequoia::testing
72 template<
class Char,
class Traits>
75 using string_view_type = std::basic_string_view<Char, Traits>;
77 template<
class Allocator>
78 using string_type = std::basic_string<Char, Traits, Allocator>;
80 using iter_type =
typename string_view_type::const_iterator;
81 using size_type =
typename string_view_type::size_type;
83 static void appender(std::string& mess, string_view_type sv, size_type pos, size_type count)
85 if constexpr(
sizeof(char) >=
sizeof(Char))
87 mess.append(sv.substr(pos, count));
91 if(pos > sv.size())
throw std::out_of_range{
"pos out of range"};
92 const auto end{count > sv.size() - pos ? sv.size() : pos + count};
93 std::ranges::transform(sv.begin() + pos, sv.begin() + end, std::back_inserter(mess), [](Char c) { return static_cast<char>(c); });
97 template<
class Advisor>
98 static auto make_advisor(std::string_view info, string_view_type obtained, string_view_type prediction, size_type pos,
const tutor<Advisor>& advisor)
100 constexpr size_type defaultOffset{30}, defaultCount{60}, npos{string_view_type::npos};
101 const auto sz{std::ranges::min(obtained.size(), prediction.size())};
103 auto newlineBackwards{ [pos](string_view_type sv){
return pos < sv.size() ? sv.rfind(
'\n', pos) : npos; } };
105 const auto loc{std::ranges::min(newlineBackwards(prediction), newlineBackwards(obtained))};
107 const size_type offset{loc < npos ? std::ranges::min(defaultOffset, pos - loc) : defaultOffset};
109 const auto lpos{pos < offset ? 0 :
110 pos < sz ? pos - offset : sz - std::ranges::min(sz, offset)};
112 struct message{ std::string mess;
bool trunc{}; };
115 [](string_view_type sv, size_type lpos) -> message {
116 std::string mess{lpos > 0 ?
"..." :
""};
118 const bool newline{(lpos < sv.size()) && (sv[lpos] ==
'\n')};
119 if(newline) mess.append(
"\\n");
121 const auto rpos{sv.find(
'\n', lpos+1)};
122 const auto count{rpos == npos ? defaultCount : rpos - lpos};
124 if(newline && (count == 1))
131 appender(mess, sv, lpos, count);
134 const bool trunc{lpos + count < sv.size()};
135 if(trunc) mess.append(
"...");
137 return {mess, trunc};
141 const auto[obMess,obTrunc]{make(obtained, lpos)};
142 const auto[prMess,prTrunc]{make(prediction, lpos)};
144 const bool trunc{lpos > 0 || obTrunc || prTrunc};
145 const auto message{append_lines(info, trunc ?
"Surrounding substring(s):" :
"Full strings:",
146 prediction_message(obMess, prMess))};
148 if constexpr(std::invocable<tutor<Advisor>, Char, Char>)
152 [message, advisor] (Char a, Char b) {
154 return append_advice(m, {advisor, a, b});
162 [message](
const auto&,
const auto&) {
return message; },
169 template<test_mode Mode,
class Advisor>
172 auto iters{std::ranges::mismatch(obtained, prediction)};
174 if((iters.in1 != obtained.end()) && (iters.in2 != prediction.end()))
176 const auto dist{std::ranges::distance(obtained.begin(), iters.in1)};
177 auto adv{make_advisor(
"", obtained, prediction, dist, advisor)};
179 const auto numLines{std::count(prediction.begin(), iters.in2,
'\n')};
183 std::string m{
"First difference detected "};
184 numLines > 0 ? m.append(
"on line ").append(std::to_string(numLines+1))
185 : m.append(
"at character ").append(std::to_string(dist));
187 return m.append(
":");
191 check(equality, mess, logger, *(iters.in1), *(iters.in2), adv);
193 else if((iters.in1 != obtained.end()) || (iters.in2 != prediction.end()))
196 [&logger, obtained, prediction, &advisor](
auto begin,
auto iter, std::string_view state, std::string_view adjective){
197 const auto dist{std::ranges::distance(begin, iter)};
198 const auto info{std::string{
"First "}.append(state).append(
" character: ").append(display_character(*iter))};
199 auto adv{make_advisor(info, obtained, prediction, dist, advisor)};
201 const auto mess{append_lines(
"Lengths differ", std::string{
"Obtained string is too "}.append(adjective))};
203 check(equality, mess, logger, obtained.size(), prediction.size(), adv);
207 if(iters.in2 != prediction.end())
209 checker(prediction.begin(), iters.in2,
"missing",
"short");
211 else if(iters.in1 != obtained.end())
213 checker(obtained.begin(), iters.in1,
"excess",
"long");
218 template<test_mode Mode,
class Advisor,
class Allocator>
221 test(equality, logger, obtained, string_view_type{prediction}, advisor);
230 template<
class Char,
class Traits, alloc Allocator>
233 using string_type = std::basic_string<Char, Traits, Allocator>;
234 using string_view_type = std::basic_string_view<Char, Traits>;
236 template<test_mode Mode,
class Advisor>
241 tester::test(
equality_check_t{}, logger, string_view_type{obtained}, string_view_type{prediction}, std::move(advisor));
244 template<test_mode Mode, std::
size_t N,
class Advisor>
249 tester::test(
equality_check_t{}, logger, string_view_type{obtained}, string_view_type{prediction}, std::move(advisor));
252 template<test_mode Mode,
class Advisor>
257 tester::test(
equality_check_t{}, logger, string_view_type{obtained}, string_view_type{prediction}, std::move(advisor));
263 template<
class S,
class T>
266 template<
class CheckType, test_mode Mode,
class U,
class V,
class Advisor>
267 requires ( std::is_same_v<std::remove_cvref_t<S>, std::remove_cvref_t<U>>
268 && std::is_same_v<std::remove_cvref_t<T>, std::remove_cvref_t<V>>)
269 static void test(CheckType flavour,
test_logger<Mode>& logger,
const std::pair<S, T>& value,
const std::pair<U, V>& prediction,
const tutor<Advisor>& advisor)
271 check_elements(flavour, logger, value, prediction, std::move(advisor));
274 template<
class CheckType, test_mode Mode,
class Advisor>
277 check_elements(equality, logger, value, prediction, std::move(advisor));
281 template<
class CheckType, test_mode Mode,
class U,
class V,
class Advisor>
282 static void check_elements(CheckType,
test_logger<Mode>& logger,
const std::pair<S, T>& value,
const std::pair<U, V>& prediction,
const tutor<Advisor>& advisor)
284 check(CheckType{},
"First element of pair is incorrect", logger, value.first, prediction.first, advisor);
285 check(CheckType{},
"Second element of pair is incorrect", logger, value.second, prediction.second, advisor);
295 template<std::size_t I = 0,
class CheckType, test_mode Mode,
class... U,
class Advisor>
296 requires (I <
sizeof...(T))
297 static void check_tuple_elements(CheckType flavour,
test_logger<Mode>& logger,
const std::tuple<T...>& value,
const std::tuple<U...>& prediction,
const tutor<Advisor>& advisor)
299 check(flavour, std::format(
"Element {} of tuple incorrect", I), logger, std::get<I>(value), std::get<I>(prediction), advisor);
300 check_tuple_elements<I+1>(flavour, logger, value, prediction, advisor);
303 template<std::size_t I = 0,
class CheckType, test_mode Mode,
class... U,
class Advisor>
308 template<
class CheckType, test_mode Mode,
class... U,
class Advisor>
309 requires ((
sizeof...(T) ==
sizeof...(U)) && (std::is_same_v<std::remove_cvref_t<T>, std::remove_cvref_t<U>> && ...))
310 static void test(CheckType flavour,
test_logger<Mode>& logger,
const std::tuple<T...>& value,
const std::tuple<U...>& prediction,
const tutor<Advisor>& advisor)
312 check_tuple_elements(flavour, logger, value, prediction, advisor);
315 template<
class CheckType, test_mode Mode,
class Advisor>
318 check_tuple_elements(equality, logger, value, prediction, advisor);
323 std::string path_check_preamble(std::string_view prefix,
const std::filesystem::path& path,
const std::filesystem::path& prediction);
329 template<test_mode Mode>
330 void operator()(
test_logger<Mode>& logger,
const std::filesystem::path& file,
const std::filesystem::path& prediction)
const
332 const auto [reducedWorking, reducedPrediction] {get_reduced_file_content(file, prediction)};
334 testing::check(report_failed_read(file), logger,
static_cast<bool>(reducedWorking));
335 testing::check(report_failed_read(prediction), logger,
static_cast<bool>(reducedPrediction));
337 if(reducedWorking && reducedPrediction)
339 check(equality, path_check_preamble(
"Contents of", file, prediction), logger, reducedWorking.value(), reducedPrediction.value());
345 inline constexpr bool is_file_comparer_v{
346 std::invocable<T, test_logger<test_mode::standard>&, std::filesystem::path, std::filesystem::path>
352 template<
class DefaultComparer,
class... Comparers>
353 requires (is_file_comparer_v<DefaultComparer> && (is_file_comparer_v<Comparers> && ...))
358 constexpr static std::size_t size()
noexcept
360 return 1 +
sizeof...(Comparers);
363 template<
class... Extensions>
364 requires (
sizeof...(Extensions) == size()) && (std::is_constructible_v<std::string, Extensions> && ...)
366 : m_Factory{std::move(extensions)...}
369 template<test_mode Mode>
370 void check_file(
test_logger<Mode>& logger,
const std::filesystem::path& file,
const std::filesystem::path& prediction)
const
372 const auto checker{m_Factory.template make_or<DefaultComparer>(file.extension().string())};
373 std::visit([&logger, &file, &prediction](
auto&& fn){ fn(logger, file, prediction); },
checker);
381 template<
class DefaultComparer,
class... Comparers>
384 template<
class DefaultComparer,
class... Comparers>
403 template<
class DefaultComparer,
class... Comparers, test_mode Mode>
406 const std::filesystem::path& path,
407 const std::filesystem::path& prediction)
409 namespace fs = std::filesystem;
412 [&logger](
const fs::path& pathFinalToken,
const fs::path& predictionFinalToken)
414 return check(equality,
"Final path token", logger, pathFinalToken, predictionFinalToken);
418 check_path(logger,
checker.customizer, path, prediction, pred);
421 template<test_mode Mode>
424 test(basic_path_equivalence, logger, path, prediction);
427 template<
class DefaultComparer,
class... Comparers, test_mode Mode>
430 const std::filesystem::path& path,
431 const std::filesystem::path& prediction)
433 namespace fs = std::filesystem;
434 check_path(logger,
checker.customizer, path, prediction, [](
const fs::path&,
const fs::path&) { return true; });
437 template<test_mode Mode>
440 test(basic_path_weak_equivalence, logger, path, prediction);
443 constexpr static std::array<std::string_view, 2>
444 excluded_files{
".DS_Store",
".keep"};
446 constexpr static std::array<std::string_view, 1>
447 excluded_extensions{seqpat};
456 template<test_mode Mode,
class Customization, invocable_r<
bool, std::filesystem::path, std::filesystem::path> FinalTokenComparison>
457 static void check_path(
test_logger<Mode>& logger,
const Customization& custom,
const std::filesystem::path& path,
const std::filesystem::path& prediction, FinalTokenComparison compare)
459 namespace fs = std::filesystem;
461 const auto pathType{fs::status(path).type()};
462 const auto predictionType{fs::status(prediction).type()};
464 if(
check(equality, path_check_preamble(
"Path type", path, prediction), logger, pathType, predictionType))
468 const auto pathFinalToken{back(path)};
469 const auto predictionFinalToken{back(prediction)};
470 if(compare(pathFinalToken, predictionFinalToken))
474 case fs::file_type::regular:
475 check_file(logger, custom, path, prediction);
477 case fs::file_type::directory:
478 check_directory(logger, custom, path, prediction, compare);
481 throw std::logic_error{std::string{
"Detailed equivalance check for paths of type '"}
489 template<test_mode Mode,
class Customization, invocable_r<
bool, std::filesystem::path, std::filesystem::path> FinalTokenComparison>
490 static void check_directory(
test_logger<Mode>& logger,
const Customization& custom,
const std::filesystem::path& dir,
const std::filesystem::path& prediction, FinalTokenComparison compare)
492 namespace fs = std::filesystem;
495 [](
const fs::path& dir) {
496 std::vector<fs::path> paths{};
497 for(
const auto& p : fs::directory_iterator(dir))
499 if( std::ranges::find(excluded_files, p.path().filename()) == excluded_files.end()
500 && std::ranges::find(excluded_extensions, p.path().extension()) == excluded_extensions.end())
506 std::ranges::sort(paths);
512 const std::vector<fs::path> paths{generator(dir)}, predictedPaths{generator(prediction)};
514 check(equality, std::string{
"Number of directory entries for "}.append(dir.generic_string()),
517 predictedPaths.size());
519 const auto iters{std::ranges::mismatch(paths, predictedPaths,
520 [&dir,&prediction](
const fs::path& lhs,
const fs::path& rhs) {
521 return fs::relative(lhs, dir) == fs::relative(rhs, prediction);
523 if((iters.in1 != paths.end()) && (iters.in2 != predictedPaths.end()))
525 check(equality,
"First directory entry mismatch", logger, *iters.in1, *iters.in2);
527 else if(iters.in1 != paths.end())
529 check(equality,
"First directory entry mismatch", logger, *iters.in1, fs::path{});
531 else if(iters.in2 != predictedPaths.end())
533 check(equality,
"First directory entry mismatch", logger, fs::path{}, *iters.in2);
537 for(std::size_t i{}; i < paths.size(); ++i)
539 check_path(logger, custom, paths[i], predictedPaths[i], compare);
544 template<test_mode Mode,
class Customization>
545 static void check_file(
test_logger<Mode>& logger,
const Customization& custom,
const std::filesystem::path& file,
const std::filesystem::path& prediction)
547 custom.check_file(logger, file, prediction);
553 template<
class... Ts>
556 using type = std::variant<Ts...>;
558 template<
class CheckType, test_mode Mode,
class Advisor>
561 if(
check(equality,
"Variant Index", logger, obtained.index(), prediction.index()))
563 check_value(flavour, logger, obtained, prediction, advisor, std::make_index_sequence<
sizeof...(Ts)>());
567 template<
class CheckType, test_mode Mode,
class Advisor, std::size_t... I>
568 static void check_value(CheckType flavour,
test_logger<Mode>& logger,
const type& obtained,
const type& prediction,
const tutor<Advisor>& advisor, std::index_sequence<I...>)
570 (check_value<I>(flavour, logger, obtained, prediction, advisor), ...);
573 template<std::
size_t I,
class CheckType, test_mode Mode,
class Advisor>
576 if(
auto pObtained{std::get_if<I>(&obtained)})
578 if(
auto pPrediction{std::get_if<I>(&prediction)})
580 check(flavour,
"Variant Contents", logger, *pObtained, *pPrediction, advisor);
584 throw std::logic_error{
"Inconsistant variant access"};
595 using type = std::optional<T>;
597 template<
class CheckType, test_mode Mode,
class Advisor>
600 if(obtained && prediction)
602 check(flavour,
"Contents of optional", logger, *obtained, *prediction, advisor);
606 const bool obtainedIsNull{obtained}, predictionIsNull{prediction};
609 nullable_type_message(obtainedIsNull, predictionIsNull),
611 static_cast<bool>(obtained),
612 static_cast<bool>(prediction));
627 using type = std::any;
629 template<test_mode Mode,
class T,
class Advisor>
632 if(
check(
"Has value", logger, obtained.has_value()))
636 const auto& val{std::any_cast<T>(obtained)};
637 check(with_best_available,
"Value held by std::any", logger, val, prediction, advisor);
639 catch(
const std::bad_any_cast&)
641 check(
"std::any does not hold the expected type", logger,
false);
663 template<test_mode Mode,
class Advisor>
666 if(obtained && prediction)
668 check(with_best_available,
"Pointees differ", logger, *obtained, *prediction, advisor);
672 const auto obtainedIsNull{
static_cast<bool>(obtained)}, predictionIsNull{
static_cast<bool>(prediction)};
675 nullable_type_message(obtainedIsNull, predictionIsNull),
695 template<test_mode Mode,
class Advisor>
698 check(equality,
"Underlying pointers differ", logger, obtained.get(), prediction.get(), advisor);
703 template<test_mode Mode,
class Advisor>
706 if(obtained && prediction)
708 check(with_best_available,
"Pointees differ", logger, *obtained, *prediction, advisor);
712 const bool obtainedIsNull{obtained}, predictionIsNull{prediction};
715 nullable_type_message(obtainedIsNull, predictionIsNull),
717 static_cast<bool>(obtained),
718 static_cast<bool>(prediction));
736 using type = std::unique_ptr<T>;
740 template<test_mode Mode,
class Advisor>
744 base_t::test_pointees(logger, obtained, prediction, advisor);
761 using type = std::shared_ptr<T>;
765 template<test_mode Mode,
class Advisor>
768 base_t::test_pointees(logger, obtained, prediction, advisor);
781 using type = std::weak_ptr<T>;
783 template<test_mode Mode>
786 check(equality,
"Underlying pointers differ", logger, obtained.lock(), prediction.lock());
789 template<test_mode Mode>
792 check(equivalence,
"Underlying pointers differ", logger, obtained.lock(), prediction.lock());
802 template<
class R,
class... Args>
805 using type = std::function<R (Args...)>;
807 template<test_mode Mode>
810 const bool obtainedIsNull{obtained}, predictionIsNull{prediction};
812 check(nullable_type_message(obtainedIsNull, predictionIsNull),
814 (obtainedIsNull && predictionIsNull) || (!obtainedIsNull && !predictionIsNull));
824 template<
class Clock,
class Duration>
827 using type = std::chrono::time_point<Clock, Duration>;
829 template<test_mode Mode,
class Advisor>
832 using ns = std::chrono::nanoseconds;
836 std::chrono::duration_cast<ns>(obtained.time_since_epoch()),
837 std::chrono::duration_cast<ns>(prediction.time_since_epoch()), advisor);
Factory implementation(s)
Contains utilities for automatically editing certain files as part of the test creation process.
File paths and related utilities.
Extensions to the std::filesystem library.
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 reading/writing to files.
Exposes elementary check methods, with the option to plug in arbitrary Extenders to compose functiona...
Definition: FreeCheckers.hpp:708
A file checker, which accepts a variadic set of file comparison function objects.
Definition: ConcreteTypeCheckers.hpp:355
Definition: TestLogger.hpp:183
class template used to wrap function objects which proffer advice.
Definition: Advice.hpp:127
Definition: FreeCheckers.hpp:82
Definition: FreeCheckers.hpp:87
Definition: FreeCheckers.hpp:107
Specialize this struct template to provide custom serialization of a given class. .
Definition: CoreInfrastructure.hpp:28
Helper for testing smart pointers.
Definition: ConcreteTypeCheckers.hpp:692
Function object for comparing files via reading their contents into strings.
Definition: ConcreteTypeCheckers.hpp:328
class template, specializations of which implement various comparisons for the specified type.
Definition: FreeCheckers.hpp:78