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 if constexpr(std::invocable<tutor<Advisor>, Char, Char>)
104 [=, &advisor] (Char a, Char b) {
105 auto m{build_preliminary_message(info, obtained, prediction, pos)};
106 return append_advice(m, {advisor, a, b});
115 [=](
const auto&,
const auto&) {
116 return build_preliminary_message(info, obtained, prediction, pos);
124 static std::string build_preliminary_message(std::string_view info, string_view_type obtained, string_view_type prediction, size_type pos)
126 constexpr size_type defaultOffset{30}, defaultCount{60}, npos{string_view_type::npos};
127 const auto sz{std::ranges::min(obtained.size(), prediction.size())};
129 auto newlineBackwards{ [pos](string_view_type sv){
return pos < sv.size() ? sv.rfind(
'\n', pos) : npos; } };
131 const auto loc{std::ranges::min(newlineBackwards(prediction), newlineBackwards(obtained))};
133 const size_type offset{loc < npos ? std::ranges::min(defaultOffset, pos - loc) : defaultOffset};
135 const auto startPos{pos < offset ? 0 :
136 pos < sz ? pos - offset :
137 sz - std::ranges::min(sz, offset)};
139 struct message{ std::string mess;
bool trunc{}; };
142 [](string_view_type sv, size_type lpos) -> message {
143 std::string mess{lpos > 0 ?
"..." :
""};
145 const bool newline{(lpos < sv.size()) && (sv[lpos] ==
'\n')};
146 if(newline) mess.append(
"\\n");
148 const auto rpos{sv.find(
'\n', lpos+1)};
149 const auto count{rpos == npos ? defaultCount : rpos - lpos};
151 if(newline && (count == 1))
158 appender(mess, sv, lpos, count);
161 const bool trunc{lpos + count < sv.size()};
162 if(trunc) mess.append(
"...");
164 return {mess, trunc};
168 const auto[obMess,obTrunc]{make(obtained , startPos)};
169 const auto[prMess,prTrunc]{make(prediction, startPos)};
171 const bool trunc{startPos > 0 || obTrunc || prTrunc};
172 return append_lines(info, trunc ?
"Surrounding substring(s):" :
"Full strings:", prediction_message(obMess, prMess));
175 template<test_mode Mode,
class Advisor>
178 auto iters{std::ranges::mismatch(obtained, prediction)};
180 if((iters.in1 != obtained.end()) && (iters.in2 != prediction.end()))
182 const auto dist{std::ranges::distance(obtained.begin(), iters.in1)};
183 auto adv{make_advisor(
"", obtained, prediction, dist, advisor)};
185 const auto numLines{std::count(prediction.begin(), iters.in2,
'\n')};
189 std::string m{
"First difference detected "};
190 numLines > 0 ? m.append(
"on line ").append(std::to_string(numLines+1))
191 : m.append(
"at character ").append(std::to_string(dist));
193 return m.append(
":");
197 check(equality, mess, logger, *(iters.in1), *(iters.in2), adv);
199 else if((iters.in1 != obtained.end()) || (iters.in2 != prediction.end()))
202 [&logger, obtained, prediction, &advisor](
auto begin,
auto iter, std::string_view state, std::string_view adjective){
203 const auto dist{std::ranges::distance(begin, iter)};
204 const auto info{std::string{
"First "}.append(state).append(
" character: ").append(display_character(*iter))};
205 auto adv{make_advisor(info, obtained, prediction, dist, advisor)};
207 const auto mess{append_lines(
"Lengths differ", std::string{
"Obtained string is too "}.append(adjective))};
209 check(equality, mess, logger, obtained.size(), prediction.size(), adv);
213 if(iters.in2 != prediction.end())
215 checker(prediction.begin(), iters.in2,
"missing",
"short");
217 else if(iters.in1 != obtained.end())
219 checker(obtained.begin(), iters.in1,
"excess",
"long");
224 template<test_mode Mode,
class Advisor,
class Allocator>
227 test(equality, logger, obtained, string_view_type{prediction}, advisor);
236 template<
class Char,
class Traits, alloc Allocator>
239 using string_type = std::basic_string<Char, Traits, Allocator>;
240 using string_view_type = std::basic_string_view<Char, Traits>;
242 template<test_mode Mode,
class Advisor>
247 tester::test(
equality_check_t{}, logger, string_view_type{obtained}, string_view_type{prediction}, std::move(advisor));
250 template<test_mode Mode, std::
size_t N,
class Advisor>
255 tester::test(
equality_check_t{}, logger, string_view_type{obtained}, string_view_type{prediction}, std::move(advisor));
258 template<test_mode Mode,
class Advisor>
263 tester::test(
equality_check_t{}, logger, string_view_type{obtained}, string_view_type{prediction}, std::move(advisor));
269 template<
class S,
class T>
272 template<
class CheckType, test_mode Mode,
class U,
class V,
class Advisor>
273 requires ( std::is_same_v<std::remove_cvref_t<S>, std::remove_cvref_t<U>>
274 && std::is_same_v<std::remove_cvref_t<T>, std::remove_cvref_t<V>>)
275 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)
277 check_elements(flavour, logger, value, prediction, std::move(advisor));
280 template<
class CheckType, test_mode Mode,
class Advisor>
283 check_elements(equality, logger, value, prediction, std::move(advisor));
287 template<
class CheckType, test_mode Mode,
class U,
class V,
class Advisor>
288 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)
290 check(CheckType{},
"First element of pair is incorrect", logger, value.first, prediction.first, advisor);
291 check(CheckType{},
"Second element of pair is incorrect", logger, value.second, prediction.second, advisor);
301 template<std::size_t I = 0,
class CheckType,
test_mode Mode,
class... U,
class Advisor>
302 requires (I <
sizeof...(T))
303 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)
305 check(flavour, std::format(
"Element {} of tuple incorrect", I), logger, std::get<I>(value), std::get<I>(prediction), advisor);
306 check_tuple_elements<I+1>(flavour, logger, value, prediction, advisor);
309 template<std::size_t I = 0,
class CheckType,
test_mode Mode,
class... U,
class Advisor>
314 template<
class CheckType,
test_mode Mode,
class... U,
class Advisor>
315 requires ((
sizeof...(T) ==
sizeof...(U)) && (std::is_same_v<std::remove_cvref_t<T>, std::remove_cvref_t<U>> && ...))
316 static void test(CheckType flavour,
test_logger<Mode>& logger,
const std::tuple<T...>& value,
const std::tuple<U...>& prediction,
const tutor<Advisor>& advisor)
318 check_tuple_elements(flavour, logger, value, prediction, advisor);
321 template<
class CheckType, test_mode Mode,
class Advisor>
324 check_tuple_elements(equality, logger, value, prediction, advisor);
329 std::string path_check_preamble(std::string_view prefix,
const std::filesystem::path& path,
const std::filesystem::path& prediction);
335 template<test_mode Mode>
336 void operator()(
test_logger<Mode>& logger,
const std::filesystem::path& file,
const std::filesystem::path& prediction)
const
338 const auto [reducedWorking, reducedPrediction] {get_reduced_file_content(file, prediction)};
340 testing::check(report_failed_read(file), logger,
static_cast<bool>(reducedWorking));
341 testing::check(report_failed_read(prediction), logger,
static_cast<bool>(reducedPrediction));
343 if(reducedWorking && reducedPrediction)
345 check(equality, path_check_preamble(
"Contents of", file, prediction), logger, reducedWorking.value(), reducedPrediction.value());
351 inline constexpr bool is_file_comparer_v{
352 std::invocable<T, test_logger<test_mode::standard>&, std::filesystem::path, std::filesystem::path>
358 template<
class DefaultComparer,
class... Comparers>
359 requires (is_file_comparer_v<DefaultComparer> && (is_file_comparer_v<Comparers> && ...))
364 constexpr static std::size_t size()
noexcept
366 return 1 +
sizeof...(Comparers);
369 template<
class... Extensions>
370 requires (
sizeof...(Extensions) == size()) && (std::is_constructible_v<std::string, Extensions> && ...)
372 : m_Factory{std::move(extensions)...}
375 template<test_mode Mode>
376 void check_file(
test_logger<Mode>& logger,
const std::filesystem::path& file,
const std::filesystem::path& prediction)
const
378 const auto checker{m_Factory.template make_or<DefaultComparer>(file.extension().string())};
379 std::visit([&logger, &file, &prediction](
auto&& fn){ fn(logger, file, prediction); },
checker);
387 template<
class DefaultComparer,
class... Comparers>
390 template<
class DefaultComparer,
class... Comparers>
409 template<
class DefaultComparer,
class... Comparers,
test_mode Mode>
412 const std::filesystem::path& path,
413 const std::filesystem::path& prediction)
415 namespace fs = std::filesystem;
418 [&logger](
const fs::path& pathFinalToken,
const fs::path& predictionFinalToken)
420 return check(equality,
"Final path token", logger, pathFinalToken, predictionFinalToken);
424 check_path(logger,
checker.customizer, path, prediction, pred);
427 template<test_mode Mode>
430 test(basic_path_equivalence, logger, path, prediction);
433 template<
class DefaultComparer,
class... Comparers,
test_mode Mode>
436 const std::filesystem::path& path,
437 const std::filesystem::path& prediction)
439 namespace fs = std::filesystem;
440 check_path(logger,
checker.customizer, path, prediction, [](
const fs::path&,
const fs::path&) { return true; });
443 template<test_mode Mode>
446 test(basic_path_weak_equivalence, logger, path, prediction);
449 constexpr static std::array<std::string_view, 2>
450 excluded_files{
".DS_Store",
".keep"};
452 constexpr static std::array<std::string_view, 1>
453 excluded_extensions{seqpat};
462 template<test_mode Mode,
class Customization, invocable_r<
bool, std::filesystem::path, std::filesystem::path> FinalTokenComparison>
463 static void check_path(
test_logger<Mode>& logger,
const Customization& custom,
const std::filesystem::path& path,
const std::filesystem::path& prediction, FinalTokenComparison compare)
465 namespace fs = std::filesystem;
467 const auto pathType{fs::status(path).type()};
468 const auto predictionType{fs::status(prediction).type()};
470 if(
check(equality, path_check_preamble(
"Path type", path, prediction), logger, pathType, predictionType))
474 const auto pathFinalToken{back(path)};
475 const auto predictionFinalToken{back(prediction)};
476 if(compare(pathFinalToken, predictionFinalToken))
480 case fs::file_type::regular:
481 check_file(logger, custom, path, prediction);
483 case fs::file_type::directory:
484 check_directory(logger, custom, path, prediction, compare);
487 throw std::logic_error{std::string{
"Detailed equivalance check for paths of type '"}
495 template<test_mode Mode,
class Customization, invocable_r<
bool, std::filesystem::path, std::filesystem::path> FinalTokenComparison>
496 static void check_directory(
test_logger<Mode>& logger,
const Customization& custom,
const std::filesystem::path& dir,
const std::filesystem::path& prediction, FinalTokenComparison compare)
498 namespace fs = std::filesystem;
501 [](
const fs::path& dirPath) {
502 std::vector<fs::path> paths{};
503 for(
const auto& p : fs::directory_iterator(dirPath))
505 if( std::ranges::find(excluded_files, p.path().filename()) == excluded_files.end()
506 && std::ranges::find(excluded_extensions, p.path().extension()) == excluded_extensions.end())
512 std::ranges::sort(paths);
518 const std::vector<fs::path> paths{generator(dir)}, predictedPaths{generator(prediction)};
522 std::string{
"Number of directory entries for "}.append(dir.generic_string()),
525 predictedPaths.size()
528 const auto iters{std::ranges::mismatch(paths, predictedPaths,
529 [&dir,&prediction](
const fs::path& lhs,
const fs::path& rhs) {
530 return fs::relative(lhs, dir) == fs::relative(rhs, prediction);
532 if((iters.in1 != paths.end()) && (iters.in2 != predictedPaths.end()))
534 check(equality,
"First directory entry mismatch", logger, *iters.in1, *iters.in2);
536 else if(iters.in1 != paths.end())
538 check(equality,
"First directory entry mismatch", logger, *iters.in1, fs::path{});
540 else if(iters.in2 != predictedPaths.end())
542 check(equality,
"First directory entry mismatch", logger, fs::path{}, *iters.in2);
546 for(std::size_t i{}; i < paths.size(); ++i)
548 check_path(logger, custom, paths[i], predictedPaths[i], compare);
553 template<test_mode Mode,
class Customization>
554 static void check_file(
test_logger<Mode>& logger,
const Customization& custom,
const std::filesystem::path& file,
const std::filesystem::path& prediction)
556 custom.check_file(logger, file, prediction);
562 template<
class... Ts>
565 using type = std::variant<Ts...>;
567 template<
class CheckType, test_mode Mode,
class Advisor>
570 if(
check(equality,
"Variant Index", logger, obtained.index(), prediction.index()))
572 check_value(flavour, logger, obtained, prediction, advisor, std::make_index_sequence<
sizeof...(Ts)>());
576 template<
class CheckType,
test_mode Mode,
class Advisor, std::size_t... I>
577 static void check_value(CheckType flavour,
test_logger<Mode>& logger,
const type& obtained,
const type& prediction,
const tutor<Advisor>& advisor, std::index_sequence<I...>)
579 (check_value<I>(flavour, logger, obtained, prediction, advisor), ...);
582 template<std::
size_t I,
class CheckType, test_mode Mode,
class Advisor>
585 if(
auto pObtained{std::get_if<I>(&obtained)})
587 if(
auto pPrediction{std::get_if<I>(&prediction)})
589 check(flavour,
"Variant Contents", logger, *pObtained, *pPrediction, advisor);
593 throw std::logic_error{
"Inconsistant variant access"};
604 using type = std::optional<T>;
606 template<
class CheckType, test_mode Mode,
class Advisor>
609 if(obtained && prediction)
611 check(flavour,
"Contents of optional", logger, *obtained, *prediction, advisor);
615 const bool obtainedIsNull{obtained}, predictionIsNull{prediction};
618 nullable_type_message(obtainedIsNull, predictionIsNull),
620 static_cast<bool>(obtained),
621 static_cast<bool>(prediction));
636 using type = std::any;
638 template<test_mode Mode,
class T,
class Advisor>
641 if(
check(
"Has value", logger, obtained.has_value()))
645 const auto& val{std::any_cast<T>(obtained)};
646 check(with_best_available,
"Value held by std::any", logger, val, prediction, advisor);
648 catch(
const std::bad_any_cast&)
650 check(
"std::any does not hold the expected type", logger,
false);
672 template<test_mode Mode,
class Advisor>
675 if(obtained && prediction)
677 check(with_best_available,
"Pointees differ", logger, *obtained, *prediction, advisor);
681 const auto obtainedIsNull{
static_cast<bool>(obtained)}, predictionIsNull{
static_cast<bool>(prediction)};
684 nullable_type_message(obtainedIsNull, predictionIsNull),
704 template<test_mode Mode,
class Advisor>
707 check(equality,
"Underlying pointers differ", logger, obtained.get(), prediction.get(), advisor);
712 template<test_mode Mode,
class Advisor>
715 if(obtained && prediction)
717 check(with_best_available,
"Pointees differ", logger, *obtained, *prediction, advisor);
721 const bool obtainedIsNull{obtained}, predictionIsNull{prediction};
724 nullable_type_message(obtainedIsNull, predictionIsNull),
726 static_cast<bool>(obtained),
727 static_cast<bool>(prediction));
745 using type = std::unique_ptr<T>;
749 template<test_mode Mode,
class Advisor>
752 base_t::test_pointees(logger, obtained, prediction, advisor);
769 using type = std::shared_ptr<T>;
773 template<test_mode Mode,
class Advisor>
776 base_t::test_pointees(logger, obtained, prediction, advisor);
789 using type = std::weak_ptr<T>;
791 template<test_mode Mode>
794 check(equality,
"Underlying pointers differ", logger, obtained.lock(), prediction.lock());
797 template<test_mode Mode>
800 check(equivalence,
"Underlying pointers differ", logger, obtained.lock(), prediction.lock());
810 template<
class R,
class... Args>
813 using type = std::function<R (Args...)>;
815 template<test_mode Mode>
818 const bool obtainedIsNull{obtained}, predictionIsNull{prediction};
820 check(nullable_type_message(obtainedIsNull, predictionIsNull),
822 (obtainedIsNull && predictionIsNull) || (!obtainedIsNull && !predictionIsNull));
832 template<
class Clock,
class Duration>
835 using type = std::chrono::time_point<Clock, Duration>;
837 template<test_mode Mode,
class Advisor>
840 using ns = std::chrono::nanoseconds;
844 std::chrono::duration_cast<ns>(obtained.time_since_epoch()),
845 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:379
Utilities for reading/writing to files.
test_mode
Specifies whether tests are run as standard tests or in false postive/negative mode.
Definition: TestMode.hpp:20
Exposes elementary check methods, with the option to plug in arbitrary Extenders to compose functiona...
Definition: FreeCheckers.hpp:710
A file checker, which accepts a variadic set of file comparison function objects.
Definition: ConcreteTypeCheckers.hpp:361
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:701
Function object for comparing files via reading their contents into strings.
Definition: ConcreteTypeCheckers.hpp:334
class template, specializations of which implement various comparisons for the specified type.
Definition: FreeCheckers.hpp:78