stringification.cpp (6585B)
1 #ifdef _MSC_VER 2 __pragma(warning(push)) 3 __pragma(warning(disable : 4643)) 4 namespace std { 5 template <typename> struct char_traits; 6 template <typename, typename> class basic_ostream; 7 typedef basic_ostream<char, char_traits<char>> ostream; // NOLINT(modernize-use-using) 8 template<class TRAITS> 9 basic_ostream<char, TRAITS>& operator<<(basic_ostream<char, TRAITS>&, const char*); 10 } 11 __pragma(warning(pop)) 12 #else 13 #include <iostream> 14 #endif 15 16 namespace N { 17 struct A { }; 18 struct B { 19 friend std::ostream& operator<<(std::ostream& os, const B&) { return os << "B"; } 20 }; 21 struct C { }; 22 static std::ostream& operator<<(std::ostream& os, const C&) { return os << "C"; } 23 } 24 25 static std::ostream& operator<<(std::ostream& os, const N::A&) { return os << "A"; } 26 27 #include <doctest/doctest.h> 28 29 #include <utility> 30 31 TEST_CASE("operator<<") { 32 MESSAGE(N::A{ }); 33 MESSAGE(N::B{ }); 34 MESSAGE(N::C{ }); 35 } 36 37 #include "header.h" 38 39 // std::move is broken with VS <= 15 40 #if defined(_MSC_VER) && _MSC_VER <= 1900 41 #define MOVE(...) __VA_ARGS__ 42 #else 43 #define MOVE std::move 44 #endif 45 46 TEST_CASE("no headers") { 47 char chs[] = { '1', 'a', 's' }; // NOLINT(*-avoid-c-arrays) 48 MESSAGE(chs); CHECK(chs == nullptr); 49 MESSAGE("1as"); CHECK("1as" == nullptr); 50 51 int ints[] = { 0, 1, 1, 2, 3, 5, 8, 13 }; // NOLINT(*-avoid-c-arrays) 52 MESSAGE(ints); CHECK(ints == nullptr); 53 MESSAGE(MOVE(ints)); // NOLINT(*-move-const-arg) 54 55 char* cptr = reinterpret_cast<char*>(ints + 4); // NOLINT 56 const char* ccptr = cptr; 57 void* vptr = reinterpret_cast<void*>(cptr); 58 CHECK(doctest::toString(cptr) == doctest::toString(ccptr)); 59 CHECK(doctest::toString(ccptr) == doctest::toString(vptr)); 60 61 char* cnptr = nullptr; 62 MESSAGE(cnptr); CHECK(cnptr != nullptr); 63 64 enum Test { 65 A = 0, B, C = 100, 66 }; 67 MESSAGE(A); CHECK(A == C); 68 69 MESSAGE(doctest::toString<int>()); 70 } 71 72 DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN 73 #include <string> 74 #include <vector> 75 #include <list> 76 #include <sstream> 77 #include <limits> 78 DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END 79 80 DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation diagnostics 81 82 // the standard forbids writing in the std namespace but it works on all compilers 83 namespace std // NOLINT(cert-dcl58-cpp) 84 { 85 template <typename T> 86 ostream& operator<<(ostream& stream, const vector<T>& in) { 87 stream << "["; 88 for (size_t i = 0; i < in.size(); ++i) { 89 if (i != 0) { stream << ", "; } 90 stream << in[i]; 91 } 92 stream << "]"; 93 return stream; 94 } 95 } 96 97 // as an alternative you may write a specialization of doctest::StringMaker 98 namespace doctest 99 { 100 template <typename T> 101 struct StringMaker<std::list<T>> 102 { 103 static String convert(const std::list<T>& in) { 104 std::ostringstream oss; 105 106 oss << "["; 107 // NOLINTNEXTLINE(*-use-auto) 108 for (typename std::list<T>::const_iterator it = in.begin(); it != in.end();) { 109 oss << *it; 110 if (++it != in.end()) { oss << ", "; } 111 } 112 oss << "]"; 113 return oss.str().c_str(); 114 } 115 }; 116 } 117 118 template <typename T, typename K> 119 struct MyType 120 { 121 T one; 122 K two; 123 }; 124 125 template <typename T> 126 struct MyTypeInherited : MyType<T, unsigned> 127 {}; 128 129 template <typename T, typename K> 130 bool operator==(const MyType<T, K>& lhs, const MyType<T, K>& rhs) { 131 return lhs.one == rhs.one && lhs.two == rhs.two; 132 } 133 134 template <typename T, typename K> 135 std::ostream& operator<<(std::ostream& stream, const MyType<T, K>& in) { 136 stream << "[" << in.one << ", " << in.two << "]"; 137 return stream; 138 } 139 140 namespace Bar 141 { 142 struct Foo 143 { 144 friend bool operator==(const Foo&, const Foo&) { return false; } 145 }; 146 147 // as a third option you may provide an overload of toString() 148 inline doctest::String toString(const Foo&) { return "Foo{}"; } 149 150 struct MyOtherType 151 { 152 int data; 153 friend bool operator==(const MyOtherType& l, const MyOtherType& r) { return l.data == r.data; } 154 }; 155 156 // you also can use a template operator<< if your code does not use std::ostream 157 template <class OStream> 158 OStream& operator<<(OStream& stream, const MyOtherType& in) { 159 stream << "MyOtherType: " << in.data; 160 return stream; 161 } 162 163 } // namespace Bar 164 165 // set an exception translator for MyTypeInherited<int> 166 REGISTER_EXCEPTION_TRANSLATOR(MyTypeInherited<int>& ex) { 167 return doctest::String("MyTypeInherited<int>(") + doctest::toString(ex.one) + ", " + 168 doctest::toString(ex.two) + ")"; 169 } 170 171 #define CHECK_NOT_DEFAULT_STR(var) CHECK(toString(var) != "{?}") 172 173 TEST_CASE("all asserts should fail and show how the objects get stringified") { 174 MyTypeInherited<int> bla1; 175 bla1.one = 5; 176 bla1.two = 4u; 177 178 Bar::Foo f1; 179 MESSAGE(f1); 180 Bar::Foo f2; 181 CHECK(f1 == f2); 182 183 doctest::String str; 184 CHECK(str == doctest::toString(str)); 185 186 // std::string already has an operator<< working with std::ostream 187 std::string dummy = "omg"; 188 189 MESSAGE(dummy); 190 191 CHECK(dummy == "tralala"); // should fail 192 CHECK("tralala" == dummy); // should fail 193 194 std::vector<int> vec1; 195 vec1.push_back(1); 196 vec1.push_back(2); 197 vec1.push_back(3); 198 199 MESSAGE(vec1); 200 201 std::vector<int> vec2; 202 vec2.push_back(1); 203 vec2.push_back(2); 204 vec2.push_back(4); 205 206 CHECK(vec1 == vec2); 207 208 std::list<int> lst_1; 209 lst_1.push_back(1); 210 lst_1.push_back(42); 211 lst_1.push_back(3); 212 213 MESSAGE(lst_1); 214 215 std::list<int> lst_2; 216 lst_2.push_back(1); 217 lst_2.push_back(2); 218 lst_2.push_back(666); 219 220 CHECK(lst_1 == lst_2); 221 222 { 223 Bar::MyOtherType s1 {42}; 224 Bar::MyOtherType s2 {666}; 225 INFO("s1=", s1, " s2=", s2); 226 CHECK(s1 == s2); 227 CHECK_MESSAGE(s1 == s2, s1, " is not really ", s2); 228 } 229 230 CHECK_NOT_DEFAULT_STR(doctest::IsNaN<double>(0.5)); 231 CHECK_NOT_DEFAULT_STR(!doctest::IsNaN<float>(std::numeric_limits<float>::infinity())); 232 CHECK_NOT_DEFAULT_STR(doctest::IsNaN<double long>(std::numeric_limits<double long>::quiet_NaN())); 233 234 CHECK("a" == doctest::Contains("aaa")); 235 236 // lets see if this exception gets translated 237 throw_if(true, bla1); 238 } 239 240 static doctest::String intTranslator(int ex) { 241 return doctest::String("int: ") + doctest::toString(ex); 242 } 243 244 TEST_CASE("a test case that registers an exception translator for int and then throws one") { 245 // set an exception translator for int - note that this shouldn't be done in a test case but 246 // in main() or somewhere before executing the tests - but here I'm just lazy... 247 doctest::registerExceptionTranslator(intTranslator); 248 249 throw_if(true, 5); 250 }