Fishnet 2.0.1
Framework for Graph-Based Analysis of GIS Vector Data
Loading...
Searching...
No Matches
TestUtil.hpp
Go to the documentation of this file.
1#pragma once
2#include <gtest/gtest.h>
3#include <ranges>
4#include <stacktrace>
5#include <string>
6#include <type_traits>
7#include <utility>
8#include <filesystem>
10
11
12namespace fishnet::test{
13
14namespace __impl{
15
16template <typename T>
17constexpr std::string_view get_type_name() {
18 std::string_view name;
19#if defined(__clang__) || defined(__GNUC__)
20 name = __PRETTY_FUNCTION__;
21#elif defined(_MSC_VER)
22 name = __FUNCSIG__;
23#else
24 return "unknown_type";
25#endif
26
27 // 1. Look for the exact starting token of the type value assignment
28 size_t start = name.find("T = ");
29 if (start != std::string_view::npos) {
30 start += 4; // Shift past "T = "
31
32 // Find where the type definition stops (either a semicolon or a bracket)
33 size_t end = name.find_first_of(";]", start);
34 if (end != std::string_view::npos) {
35 return name.substr(start, end - start);
36 }
37 }
38
39 // 2. Fallback for MSVC or non-standard configurations (searches the innermost template)
40 start = name.find_last_of('<');
41 size_t end = name.find_last_of(">]");
42 if (start != std::string_view::npos && end != std::string_view::npos && end > start) {
43 return name.substr(start + 1, end - start - 1);
44 }
45
46 return name; // Absolute fallback safety
47}
48
49template<typename T>
50concept Printable = requires(std::stringstream ss,const std::remove_cvref_t<T> & value) {
51 {ss << value};
52};
53
54template<typename T>
55concept OptionalOrExpected = requires(const T & t){
56 {t.has_value()} -> std::convertible_to<bool>;
57 {t.value()} -> std::convertible_to<typename std::remove_cvref_t<T>::value_type>;
58};
59
60template<typename T>
61concept AssociativeContainer = requires(std::remove_cvref_t<T> container, typename std::remove_cvref_t<T>::key_type key) {
62 {container.find(key)} -> std::same_as<typename std::remove_cvref_t<T>::iterator>;
63};
64
65static inline void printValue(std::stringstream & ss, const auto & value) {
66 if constexpr (Printable<std::remove_cvref_t<decltype(value)>>) {
67 ss << value;
68 } else {
69 ss << '?';
70 }
71}
72
73static std::string printContainer(std::ranges::input_range auto && container) {
74 std::pair<char, char> containerBoundary = std::make_pair('[', ']');
75 if constexpr (AssociativeContainer<std::remove_cvref_t<decltype(container)>>) {
76 containerBoundary = std::make_pair('{', '}');
77 }
78 std::stringstream ss;
79 ss << containerBoundary.first;
80 bool isFirst = true;
81 for(const auto & element : container) {
82 if(!isFirst) {
83 ss << ", ";
84 }
85 printValue(ss, element);
86 isFirst = false;
87 }
88 ss << containerBoundary.second;
89 return ss.str();
90}
91
92static auto message(auto... vals) {
93 std::stringstream ss;
94 ss << "\033[1;31m";
95 ((printValue(ss, vals)), ...);
96 ss << "\033[0m";
97 return ss.str();
98}
99
100static auto trace(){
101 std::stringstream ss;
102 ss << "\033[1;36m";
103 ss << "Stack trace:" << std::endl;
104 auto st = std::stacktrace::current();
105 auto it = st.begin();
106 while(it != st.end() && it->source_file().ends_with("TestUtil.hpp"))
107 ++it;
108 auto first = it;
109 while(not it->source_file().ends_with("gtest.cc") && it != st.end()) {
110 if (it != first)
111 ss << std::endl;
112 ss << "\t" << it->source_file() << ":" << it->source_line();
113 ++it;
114 }
115 ss << "\033[0m";
116 return ss.str();
117}
118
119static auto messageWithTrace(auto... vals) {
120 std::stringstream ss;
121 ss << message(vals...) << std::endl;
122 ss << trace();
123 return ss.str();
124}
125
126template<typename T, typename Eq = std::equal_to<>>
127static testing::AssertionResult contains(std::ranges::input_range auto && collection, const T & element, Eq const & eq = Eq{}) {
128 if(std::ranges::end(collection) != std::ranges::find_if(collection, [&element, &eq](const auto & e) { return eq(e, element); })){
129 return testing::AssertionSuccess();
130 }else{
131 return testing::AssertionFailure() << message("Collection ", printContainer(collection), " does not contain element: ", element);
132 }
133}
134
135template<typename T, typename Eq = std::equal_to<>>
136static testing::AssertionResult notContains(std::ranges::input_range auto && collection, const T & element, Eq const & eq = Eq{}) {
137 if(std::ranges::end(collection) == std::ranges::find_if(collection, [&element, &eq](const auto & e) { return eq(e, element); })){
138 return testing::AssertionSuccess();
139 }else{
140 return testing::AssertionFailure() << message("Collection ", printContainer(collection), " contains element: ", element);
141 }
142}
143
144template<typename Eq = std::equal_to<>>
145static testing::AssertionResult unsortedRangeEqual(std::ranges::forward_range auto const & actual, std::ranges::forward_range auto const & expected, Eq const & eq = Eq{}) {
146 using A = std::ranges::range_value_t<decltype(actual)>;
147 using E = std::ranges::range_value_t<decltype(expected)>;
148 static_assert(fishnet::util::BiFunction<decltype(eq), A, E,bool>, "eq must be a binary function that takes two arguments of type A and E");
149 if(fishnet::util::size(actual) != fishnet::util::size(expected)){
150 return testing::AssertionFailure() << message("Ranges have a different size!\nExpecting: ", fishnet::util::size(expected), " but was: ", fishnet::util::size(actual));
151 }
152 for(const auto & expectedElement : expected){
153 auto result = contains(actual, expectedElement, eq);
154 if(!result){
155 return result;
156 }
157 }
158 return testing::AssertionSuccess() << message("Ranges are equal (ignoring order)");
159}
160
161template<typename Eq = std::equal_to<>>
162static testing::AssertionResult sortedRangeEqual(std::ranges::forward_range auto const & actual, std::ranges::forward_range auto const & expected, Eq const & eq = Eq{}) {
163 using A = std::ranges::range_value_t<decltype(actual)>;
164 using E = std::ranges::range_value_t<decltype(expected)>;
165 static_assert(std::convertible_to<A,E>, "Actual and expected collection must have convertible value type");
166 static_assert(fishnet::util::BiFunction<decltype(eq), A, E,bool>, "eq must be a binary function that takes two arguments of type A and E");
167 auto actualIt = std::ranges::cbegin(actual);
168 auto expectedIt = std::ranges::cbegin(expected);
169 while(actualIt != std::ranges::cend(actual) && expectedIt != std::ranges::cend(expected)){
170 if(!eq(*actualIt,*expectedIt)){
171 if constexpr (Printable<A> && Printable<E>) {
172 return testing::AssertionFailure() << message("Ranges differ at index ", std::distance(std::ranges::cbegin(actual), actualIt), "! Expecting ", *expectedIt, " but was: ", *actualIt);
173 } else {
174 return testing::AssertionFailure() << message("Ranges differ at index ", std::distance(std::ranges::cbegin(actual), actualIt));
175 }
176 }
177 ++actualIt;
178 ++expectedIt;
179 }
180 if(actualIt != std::ranges::cend(actual) or expectedIt != std::ranges::cend(expected)) {
181 return testing::AssertionFailure() << message("Ranges have a different size!", "Expecting: ", fishnet::util::size(expected), " but was: ", fishnet::util::size(actual));
182 }
183 return testing::AssertionSuccess() << message("Ranges are equal (in order)");
184}
185} // namespace fishnet::test::__impl
186
187static void TODO() {
188 FAIL() << __impl::messageWithTrace("Test not implemented yet");
189}
190
191template<typename T>
192static void EXPECT_CONTAINS(fishnet::util::input_range_of<T> auto && collection, const T & element) {
193 EXPECT_TRUE(__impl::contains(collection,element)) << __impl::trace();
194}
195
196template<typename T>
197static void EXPECT_NOT_CONTAINS(fishnet::util::input_range_of<T> auto && collection, const T & element) {
198 EXPECT_TRUE(__impl::notContains(collection,element)) << __impl::trace();
199}
200
201static void EXPECT_CONTAINS_ALL(std::ranges::input_range auto && collection, std::ranges::input_range auto && expected){
202 for(const auto & element : expected){
203 EXPECT_TRUE(__impl::contains(collection,element)) << __impl::trace();
204 }
205}
206
207template<typename... Args>
208static void EXPECT_CONTAINS_ALL(std::ranges::input_range auto && collection, const Args & ...args){
209 (EXPECT_CONTAINS(collection,args), ...);
210}
211
212static void EXPECT_UNSORTED_RANGE_EQ(std::ranges::forward_range auto const & actual, std::ranges::forward_range auto const & expected) {
213 EXPECT_TRUE(__impl::unsortedRangeEqual(actual, expected)) << __impl::trace();
214}
215
216static void EXPECT_UNSORTED_RANGE_EQ(std::ranges::forward_range auto const & actual, std::ranges::forward_range auto const & expected, auto const & eq) {
217 EXPECT_TRUE(__impl::unsortedRangeEqual(actual, expected, eq)) << __impl::trace();
218}
219
220static void EXPECT_SORTED_RANGE_EQ(std::ranges::forward_range auto const & actual, std::ranges::forward_range auto const & expected) {
221 EXPECT_TRUE(__impl::sortedRangeEqual(actual, expected)) << __impl::trace();
222}
223
224static void EXPECT_SORTED_RANGE_EQ(std::ranges::forward_range auto const & actual, std::ranges::forward_range auto const & expected, auto const & eq) {
225 EXPECT_TRUE(__impl::sortedRangeEqual(actual, expected, eq)) << __impl::trace();
226}
227
228static void EXPECT_SIZE(std::ranges::range auto && range, size_t expectedSize) {
229 EXPECT_EQ(fishnet::util::size(range),expectedSize) << __impl::messageWithTrace("Expected size of range: ", expectedSize, " but was: ", fishnet::util::size(range));
230}
231
232static void EXPECT_EMPTY(std::ranges::range auto && range) {
233 EXPECT_EQ(fishnet::util::size(range), 0) << __impl::messageWithTrace("Expected range to be empty but was: ", __impl::printContainer(range));
234}
235
236template<typename ExpectedType>
237static void EXPECT_TYPE(const auto & value){
238 if constexpr (not std::is_same_v<std::remove_cvref_t<decltype(value)>, ExpectedType>){
239 constexpr std::string_view expectedTypeName = __impl::get_type_name<ExpectedType>();
240 constexpr std::string_view actualTypeName = __impl::get_type_name<std::remove_cvref_t<decltype(value)>>();
241 FAIL() << __impl::messageWithTrace("Expected type: '", expectedTypeName, "' but was: '", actualTypeName, "'");
242 }else{
243 SUCCEED();
244 }
245}
246
247static void EXPECT_VALUE(const __impl::OptionalOrExpected auto & wrapper, const typename std::remove_cvref_t<decltype(wrapper)>::value_type & expectedValue){
248 EXPECT_TRUE(wrapper.has_value()) << __impl::messageWithTrace("Expected wrapper to have value: '", expectedValue, "' but was empty");
249 if(wrapper.has_value()){
250 if(wrapper.value() == expectedValue){
251 SUCCEED();
252 }else{
253 FAIL() << __impl::messageWithTrace("Wrapper has value: '", wrapper.value(), "' but was expecting: '", expectedValue, "'");
254 }
255 }
256}
257
258static void EXPECT_VALUE(const __impl::OptionalOrExpected auto & wrapper){
259 EXPECT_TRUE(wrapper.has_value()) << __impl::messageWithTrace("Expected wrapper to have value but was empty");
260 if(wrapper.has_value()){
261 SUCCEED();
262 }
263}
264
265static void ASSERT_VALUE(const __impl::OptionalOrExpected auto & wrapper) {
266 ASSERT_TRUE(wrapper.has_value()) << __impl::messageWithTrace("Expected wrapper to have value but was empty");
267}
268
269static void EXPECT_EMPTY(const __impl::OptionalOrExpected auto & wrapper){
270 if(wrapper.has_value()){
271 FAIL() << __impl::messageWithTrace("Expected wrapper to be empty but was: '", wrapper.value(), "'");
272 }else{
273 SUCCEED();
274 }
275}
276
277static void EXPECT_EXISTS(const std::filesystem::path & path){
278 EXPECT_TRUE(std::filesystem::exists(path)) << __impl::messageWithTrace("Expecting path ", path, " to exist");
279}
280
281static void EXPECT_NOT_EXISTS(const std::filesystem::path & path){
282 EXPECT_FALSE(std::filesystem::exists(path)) << __impl::messageWithTrace("Expecting path ", path, " to not exist");
283}
284
286 EXPECT_TRUE(value >= lower) << __impl::messageWithTrace("Expected value ", value, " to be greater than or equal to lower bound ", lower);
287 EXPECT_TRUE(value < upper) << __impl::messageWithTrace("Expected value ", value, " to be less than upper bound ", upper);
288}
289} // namespace fishnet::test
Definition NumericConcepts.hpp:7
Definition FunctionalConcepts.hpp:15
Definition CollectionConcepts.hpp:33
Definition TestUtil.hpp:12
void EXPECT_IN_RANGE(fishnet::math::Number auto value, fishnet::math::Number auto lower, fishnet::math::Number auto upper)
Definition TestUtil.hpp:285
static void EXPECT_CONTAINS(fishnet::util::input_range_of< T > auto &&collection, const T &element)
Definition TestUtil.hpp:192
static void EXPECT_EXISTS(const std::filesystem::path &path)
Definition TestUtil.hpp:277
static void EXPECT_SORTED_RANGE_EQ(std::ranges::forward_range auto const &actual, std::ranges::forward_range auto const &expected)
Definition TestUtil.hpp:220
static void EXPECT_NOT_EXISTS(const std::filesystem::path &path)
Definition TestUtil.hpp:281
static void EXPECT_VALUE(const __impl::OptionalOrExpected auto &wrapper, const typename std::remove_cvref_t< decltype(wrapper)>::value_type &expectedValue)
Definition TestUtil.hpp:247
static void TODO()
Definition TestUtil.hpp:187
static void EXPECT_SIZE(std::ranges::range auto &&range, size_t expectedSize)
Definition TestUtil.hpp:228
static void EXPECT_NOT_CONTAINS(fishnet::util::input_range_of< T > auto &&collection, const T &element)
Definition TestUtil.hpp:197
static void EXPECT_TYPE(const auto &value)
Definition TestUtil.hpp:237
static void EXPECT_CONTAINS_ALL(std::ranges::input_range auto &&collection, std::ranges::input_range auto &&expected)
Definition TestUtil.hpp:201
static void ASSERT_VALUE(const __impl::OptionalOrExpected auto &wrapper)
Definition TestUtil.hpp:265
static void EXPECT_UNSORTED_RANGE_EQ(std::ranges::forward_range auto const &actual, std::ranges::forward_range auto const &expected)
Definition TestUtil.hpp:212
static void EXPECT_EMPTY(std::ranges::range auto &&range)
Definition TestUtil.hpp:232
constexpr size_t size(std::ranges::range auto &&range) noexcept
Definition CollectionConcepts.hpp:50