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