1 /* 2 * Created by Phil on 8/5/2012. 3 * Copyright 2012 Two Blue Cubes Ltd. All rights reserved. 4 * 5 * Distributed under the Boost Software License, Version 1.0. (See accompanying 6 * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 7 */ 8 #ifndef TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED 9 #define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED 10 11 12 #include <vector> 13 #include <cstddef> 14 #include <type_traits> 15 #include <string> 16 #include "catch_compiler_capabilities.h" 17 #include "catch_stream.h" 18 19 #ifdef CATCH_CONFIG_CPP17_STRING_VIEW 20 #include <string_view> 21 #endif 22 23 #ifdef __OBJC__ 24 #include "catch_objc_arc.hpp" 25 #endif 26 27 #ifdef _MSC_VER 28 #pragma warning(push) 29 #pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless 30 #endif 31 32 namespace Catch { 33 namespace Detail { 34 35 extern const std::string unprintableString; 36 37 std::string rawMemoryToString( const void *object, std::size_t size ); 38 39 template<typename T> rawMemoryToString(const T & object)40 std::string rawMemoryToString( const T& object ) { 41 return rawMemoryToString( &object, sizeof(object) ); 42 } 43 44 template<typename T> 45 class IsStreamInsertable { 46 template<typename SS, typename TT> 47 static auto test(int) 48 -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type()); 49 50 template<typename, typename> 51 static auto test(...)->std::false_type; 52 53 public: 54 static const bool value = decltype(test<std::ostream, const T&>(0))::value; 55 }; 56 57 template<typename E> 58 std::string convertUnknownEnumToString( E e ); 59 60 template<typename T> 61 typename std::enable_if< 62 !std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value, convertUnstreamable(T const &)63 std::string>::type convertUnstreamable( T const& ) { 64 return Detail::unprintableString; 65 } 66 template<typename T> 67 typename std::enable_if< 68 !std::is_enum<T>::value && std::is_base_of<std::exception, T>::value, convertUnstreamable(T const & ex)69 std::string>::type convertUnstreamable(T const& ex) { 70 return ex.what(); 71 } 72 73 74 template<typename T> 75 typename std::enable_if< 76 std::is_enum<T>::value convertUnstreamable(T const & value)77 , std::string>::type convertUnstreamable( T const& value ) { 78 return convertUnknownEnumToString( value ); 79 } 80 81 #if defined(_MANAGED) 82 //! Convert a CLR string to a utf8 std::string 83 template<typename T> 84 std::string clrReferenceToString( T^ ref ) { 85 if (ref == nullptr) 86 return std::string("null"); 87 auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); 88 cli::pin_ptr<System::Byte> p = &bytes[0]; 89 return std::string(reinterpret_cast<char const *>(p), bytes->Length); 90 } 91 #endif 92 93 } // namespace Detail 94 95 96 // If we decide for C++14, change these to enable_if_ts 97 template <typename T, typename = void> 98 struct StringMaker { 99 template <typename Fake = T> 100 static 101 typename std::enable_if<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type convertStringMaker102 convert(const Fake& value) { 103 ReusableStringStream rss; 104 // NB: call using the function-like syntax to avoid ambiguity with 105 // user-defined templated operator<< under clang. 106 rss.operator<<(value); 107 return rss.str(); 108 } 109 110 template <typename Fake = T> 111 static 112 typename std::enable_if<!::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type convertStringMaker113 convert( const Fake& value ) { 114 #if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) 115 return Detail::convertUnstreamable(value); 116 #else 117 return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); 118 #endif 119 } 120 }; 121 122 namespace Detail { 123 124 // This function dispatches all stringification requests inside of Catch. 125 // Should be preferably called fully qualified, like ::Catch::Detail::stringify 126 template <typename T> stringify(const T & e)127 std::string stringify(const T& e) { 128 return ::Catch::StringMaker<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::convert(e); 129 } 130 131 template<typename E> convertUnknownEnumToString(E e)132 std::string convertUnknownEnumToString( E e ) { 133 return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<E>::type>(e)); 134 } 135 136 #if defined(_MANAGED) 137 template <typename T> 138 std::string stringify( T^ e ) { 139 return ::Catch::StringMaker<T^>::convert(e); 140 } 141 #endif 142 143 } // namespace Detail 144 145 // Some predefined specializations 146 147 template<> 148 struct StringMaker<std::string> { 149 static std::string convert(const std::string& str); 150 }; 151 152 #ifdef CATCH_CONFIG_CPP17_STRING_VIEW 153 template<> 154 struct StringMaker<std::string_view> { 155 static std::string convert(std::string_view str); 156 }; 157 #endif 158 159 template<> 160 struct StringMaker<char const *> { 161 static std::string convert(char const * str); 162 }; 163 template<> 164 struct StringMaker<char *> { 165 static std::string convert(char * str); 166 }; 167 168 #ifdef CATCH_CONFIG_WCHAR 169 template<> 170 struct StringMaker<std::wstring> { 171 static std::string convert(const std::wstring& wstr); 172 }; 173 174 # ifdef CATCH_CONFIG_CPP17_STRING_VIEW 175 template<> 176 struct StringMaker<std::wstring_view> { 177 static std::string convert(std::wstring_view str); 178 }; 179 # endif 180 181 template<> 182 struct StringMaker<wchar_t const *> { 183 static std::string convert(wchar_t const * str); 184 }; 185 template<> 186 struct StringMaker<wchar_t *> { 187 static std::string convert(wchar_t * str); 188 }; 189 #endif 190 191 // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer, 192 // while keeping string semantics? 193 template<int SZ> 194 struct StringMaker<char[SZ]> { 195 static std::string convert(char const* str) { 196 return ::Catch::Detail::stringify(std::string{ str }); 197 } 198 }; 199 template<int SZ> 200 struct StringMaker<signed char[SZ]> { 201 static std::string convert(signed char const* str) { 202 return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) }); 203 } 204 }; 205 template<int SZ> 206 struct StringMaker<unsigned char[SZ]> { 207 static std::string convert(unsigned char const* str) { 208 return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) }); 209 } 210 }; 211 212 template<> 213 struct StringMaker<int> { 214 static std::string convert(int value); 215 }; 216 template<> 217 struct StringMaker<long> { 218 static std::string convert(long value); 219 }; 220 template<> 221 struct StringMaker<long long> { 222 static std::string convert(long long value); 223 }; 224 template<> 225 struct StringMaker<unsigned int> { 226 static std::string convert(unsigned int value); 227 }; 228 template<> 229 struct StringMaker<unsigned long> { 230 static std::string convert(unsigned long value); 231 }; 232 template<> 233 struct StringMaker<unsigned long long> { 234 static std::string convert(unsigned long long value); 235 }; 236 237 template<> 238 struct StringMaker<bool> { 239 static std::string convert(bool b); 240 }; 241 242 template<> 243 struct StringMaker<char> { 244 static std::string convert(char c); 245 }; 246 template<> 247 struct StringMaker<signed char> { 248 static std::string convert(signed char c); 249 }; 250 template<> 251 struct StringMaker<unsigned char> { 252 static std::string convert(unsigned char c); 253 }; 254 255 template<> 256 struct StringMaker<std::nullptr_t> { 257 static std::string convert(std::nullptr_t); 258 }; 259 260 template<> 261 struct StringMaker<float> { 262 static std::string convert(float value); 263 }; 264 template<> 265 struct StringMaker<double> { 266 static std::string convert(double value); 267 }; 268 269 template <typename T> 270 struct StringMaker<T*> { 271 template <typename U> 272 static std::string convert(U* p) { 273 if (p) { 274 return ::Catch::Detail::rawMemoryToString(p); 275 } else { 276 return "nullptr"; 277 } 278 } 279 }; 280 281 template <typename R, typename C> 282 struct StringMaker<R C::*> { 283 static std::string convert(R C::* p) { 284 if (p) { 285 return ::Catch::Detail::rawMemoryToString(p); 286 } else { 287 return "nullptr"; 288 } 289 } 290 }; 291 292 #if defined(_MANAGED) 293 template <typename T> 294 struct StringMaker<T^> { 295 static std::string convert( T^ ref ) { 296 return ::Catch::Detail::clrReferenceToString(ref); 297 } 298 }; 299 #endif 300 301 namespace Detail { 302 template<typename InputIterator> 303 std::string rangeToString(InputIterator first, InputIterator last) { 304 ReusableStringStream rss; 305 rss << "{ "; 306 if (first != last) { 307 rss << ::Catch::Detail::stringify(*first); 308 for (++first; first != last; ++first) 309 rss << ", " << ::Catch::Detail::stringify(*first); 310 } 311 rss << " }"; 312 return rss.str(); 313 } 314 } 315 316 #ifdef __OBJC__ 317 template<> 318 struct StringMaker<NSString*> { 319 static std::string convert(NSString * nsstring) { 320 if (!nsstring) 321 return "nil"; 322 return std::string("@") + [nsstring UTF8String]; 323 } 324 }; 325 template<> 326 struct StringMaker<NSObject*> { 327 static std::string convert(NSObject* nsObject) { 328 return ::Catch::Detail::stringify([nsObject description]); 329 } 330 331 }; 332 namespace Detail { 333 inline std::string stringify( NSString* nsstring ) { 334 return StringMaker<NSString*>::convert( nsstring ); 335 } 336 337 } // namespace Detail 338 #endif // __OBJC__ 339 340 } // namespace Catch 341 342 ////////////////////////////////////////////////////// 343 // Separate std-lib types stringification, so it can be selectively enabled 344 // This means that we do not bring in 345 346 #if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) 347 # define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER 348 # define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER 349 # define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER 350 # define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER 351 # define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER 352 #endif 353 354 // Separate std::pair specialization 355 #if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) 356 #include <utility> 357 namespace Catch { 358 template<typename T1, typename T2> 359 struct StringMaker<std::pair<T1, T2> > { 360 static std::string convert(const std::pair<T1, T2>& pair) { 361 ReusableStringStream rss; 362 rss << "{ " 363 << ::Catch::Detail::stringify(pair.first) 364 << ", " 365 << ::Catch::Detail::stringify(pair.second) 366 << " }"; 367 return rss.str(); 368 } 369 }; 370 } 371 #endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER 372 373 #if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL) 374 #include <optional> 375 namespace Catch { 376 template<typename T> 377 struct StringMaker<std::optional<T> > { 378 static std::string convert(const std::optional<T>& optional) { 379 ReusableStringStream rss; 380 if (optional.has_value()) { 381 rss << ::Catch::Detail::stringify(*optional); 382 } else { 383 rss << "{ }"; 384 } 385 return rss.str(); 386 } 387 }; 388 } 389 #endif // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER 390 391 // Separate std::tuple specialization 392 #if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) 393 #include <tuple> 394 namespace Catch { 395 namespace Detail { 396 template< 397 typename Tuple, 398 std::size_t N = 0, 399 bool = (N < std::tuple_size<Tuple>::value) 400 > 401 struct TupleElementPrinter { 402 static void print(const Tuple& tuple, std::ostream& os) { 403 os << (N ? ", " : " ") 404 << ::Catch::Detail::stringify(std::get<N>(tuple)); 405 TupleElementPrinter<Tuple, N + 1>::print(tuple, os); 406 } 407 }; 408 409 template< 410 typename Tuple, 411 std::size_t N 412 > 413 struct TupleElementPrinter<Tuple, N, false> { 414 static void print(const Tuple&, std::ostream&) {} 415 }; 416 417 } 418 419 420 template<typename ...Types> 421 struct StringMaker<std::tuple<Types...>> { 422 static std::string convert(const std::tuple<Types...>& tuple) { 423 ReusableStringStream rss; 424 rss << '{'; 425 Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get()); 426 rss << " }"; 427 return rss.str(); 428 } 429 }; 430 } 431 #endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER 432 433 #if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) 434 #include <variant> 435 namespace Catch { 436 template<> 437 struct StringMaker<std::monostate> { 438 static std::string convert(const std::monostate&) { 439 return "{ }"; 440 } 441 }; 442 443 template<typename... Elements> 444 struct StringMaker<std::variant<Elements...>> { 445 static std::string convert(const std::variant<Elements...>& variant) { 446 if (variant.valueless_by_exception()) { 447 return "{valueless variant}"; 448 } else { 449 return std::visit( 450 [](const auto& value) { 451 return ::Catch::Detail::stringify(value); 452 }, 453 variant 454 ); 455 } 456 } 457 }; 458 } 459 #endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER 460 461 namespace Catch { 462 struct not_this_one {}; // Tag type for detecting which begin/ end are being selected 463 464 // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace 465 using std::begin; 466 using std::end; 467 468 not_this_one begin( ... ); 469 not_this_one end( ... ); 470 471 template <typename T> 472 struct is_range { 473 static const bool value = 474 !std::is_same<decltype(begin(std::declval<T>())), not_this_one>::value && 475 !std::is_same<decltype(end(std::declval<T>())), not_this_one>::value; 476 }; 477 478 #if defined(_MANAGED) // Managed types are never ranges 479 template <typename T> 480 struct is_range<T^> { 481 static const bool value = false; 482 }; 483 #endif 484 485 template<typename Range> 486 std::string rangeToString( Range const& range ) { 487 return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); 488 } 489 490 // Handle vector<bool> specially 491 template<typename Allocator> 492 std::string rangeToString( std::vector<bool, Allocator> const& v ) { 493 ReusableStringStream rss; 494 rss << "{ "; 495 bool first = true; 496 for( bool b : v ) { 497 if( first ) 498 first = false; 499 else 500 rss << ", "; 501 rss << ::Catch::Detail::stringify( b ); 502 } 503 rss << " }"; 504 return rss.str(); 505 } 506 507 template<typename R> 508 struct StringMaker<R, typename std::enable_if<is_range<R>::value && !::Catch::Detail::IsStreamInsertable<R>::value>::type> { 509 static std::string convert( R const& range ) { 510 return rangeToString( range ); 511 } 512 }; 513 514 template <typename T, int SZ> 515 struct StringMaker<T[SZ]> { 516 static std::string convert(T const(&arr)[SZ]) { 517 return rangeToString(arr); 518 } 519 }; 520 521 522 } // namespace Catch 523 524 // Separate std::chrono::duration specialization 525 #if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) 526 #include <ctime> 527 #include <ratio> 528 #include <chrono> 529 530 531 namespace Catch { 532 533 template <class Ratio> 534 struct ratio_string { 535 static std::string symbol(); 536 }; 537 538 template <class Ratio> 539 std::string ratio_string<Ratio>::symbol() { 540 Catch::ReusableStringStream rss; 541 rss << '[' << Ratio::num << '/' 542 << Ratio::den << ']'; 543 return rss.str(); 544 } 545 template <> 546 struct ratio_string<std::atto> { 547 static std::string symbol(); 548 }; 549 template <> 550 struct ratio_string<std::femto> { 551 static std::string symbol(); 552 }; 553 template <> 554 struct ratio_string<std::pico> { 555 static std::string symbol(); 556 }; 557 template <> 558 struct ratio_string<std::nano> { 559 static std::string symbol(); 560 }; 561 template <> 562 struct ratio_string<std::micro> { 563 static std::string symbol(); 564 }; 565 template <> 566 struct ratio_string<std::milli> { 567 static std::string symbol(); 568 }; 569 570 //////////// 571 // std::chrono::duration specializations 572 template<typename Value, typename Ratio> 573 struct StringMaker<std::chrono::duration<Value, Ratio>> { 574 static std::string convert(std::chrono::duration<Value, Ratio> const& duration) { 575 ReusableStringStream rss; 576 rss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's'; 577 return rss.str(); 578 } 579 }; 580 template<typename Value> 581 struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> { 582 static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) { 583 ReusableStringStream rss; 584 rss << duration.count() << " s"; 585 return rss.str(); 586 } 587 }; 588 template<typename Value> 589 struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> { 590 static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) { 591 ReusableStringStream rss; 592 rss << duration.count() << " m"; 593 return rss.str(); 594 } 595 }; 596 template<typename Value> 597 struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> { 598 static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) { 599 ReusableStringStream rss; 600 rss << duration.count() << " h"; 601 return rss.str(); 602 } 603 }; 604 605 //////////// 606 // std::chrono::time_point specialization 607 // Generic time_point cannot be specialized, only std::chrono::time_point<system_clock> 608 template<typename Clock, typename Duration> 609 struct StringMaker<std::chrono::time_point<Clock, Duration>> { 610 static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) { 611 return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; 612 } 613 }; 614 // std::chrono::time_point<system_clock> specialization 615 template<typename Duration> 616 struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> { 617 static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) { 618 auto converted = std::chrono::system_clock::to_time_t(time_point); 619 620 #ifdef _MSC_VER 621 std::tm timeInfo = {}; 622 gmtime_s(&timeInfo, &converted); 623 #else 624 std::tm* timeInfo = std::gmtime(&converted); 625 #endif 626 627 auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); 628 char timeStamp[timeStampSize]; 629 const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; 630 631 #ifdef _MSC_VER 632 std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); 633 #else 634 std::strftime(timeStamp, timeStampSize, fmt, timeInfo); 635 #endif 636 return std::string(timeStamp); 637 } 638 }; 639 } 640 #endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER 641 642 643 #ifdef _MSC_VER 644 #pragma warning(pop) 645 #endif 646 647 #endif // TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED 648