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 __OBJC__ 20 #include "catch_objc_arc.hpp" 21 #endif 22 23 #ifdef _MSC_VER 24 #pragma warning(push) 25 #pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless 26 #endif 27 28 29 // We need a dummy global operator<< so we can bring it into Catch namespace later 30 struct Catch_global_namespace_dummy {}; 31 std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); 32 33 namespace Catch { 34 // Bring in operator<< from global namespace into Catch namespace 35 using ::operator<<; 36 37 namespace Detail { 38 39 extern const std::string unprintableString; 40 41 std::string rawMemoryToString( const void *object, std::size_t size ); 42 43 template<typename T> rawMemoryToString(const T & object)44 std::string rawMemoryToString( const T& object ) { 45 return rawMemoryToString( &object, sizeof(object) ); 46 } 47 48 template<typename T> 49 class IsStreamInsertable { 50 template<typename SS, typename TT> 51 static auto test(int) 52 -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type()); 53 54 template<typename, typename> 55 static auto test(...)->std::false_type; 56 57 public: 58 static const bool value = decltype(test<std::ostream, const T&>(0))::value; 59 }; 60 61 template<typename E> 62 std::string convertUnknownEnumToString( E e ); 63 64 template<typename T> convertUnstreamable(T const & value)65 typename std::enable_if<!std::is_enum<T>::value, std::string>::type convertUnstreamable( T const& value ) { 66 #if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) 67 (void)value; 68 return Detail::unprintableString; 69 #else 70 return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); 71 #endif 72 } 73 template<typename T> convertUnstreamable(T const & value)74 typename std::enable_if<std::is_enum<T>::value, std::string>::type convertUnstreamable( T const& value ) { 75 return convertUnknownEnumToString( value ); 76 } 77 78 } // namespace Detail 79 80 81 // If we decide for C++14, change these to enable_if_ts 82 template <typename T, typename = void> 83 struct StringMaker { 84 template <typename Fake = T> 85 static 86 typename std::enable_if<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type convertStringMaker87 convert(const Fake& value) { 88 ReusableStringStream rss; 89 rss << value; 90 return rss.str(); 91 } 92 93 template <typename Fake = T> 94 static 95 typename std::enable_if<!::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type convertStringMaker96 convert( const Fake& value ) { 97 return Detail::convertUnstreamable( value ); 98 } 99 }; 100 101 namespace Detail { 102 103 // This function dispatches all stringification requests inside of Catch. 104 // Should be preferably called fully qualified, like ::Catch::Detail::stringify 105 template <typename T> stringify(const T & e)106 std::string stringify(const T& e) { 107 return ::Catch::StringMaker<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::convert(e); 108 } 109 110 template<typename E> convertUnknownEnumToString(E e)111 std::string convertUnknownEnumToString( E e ) { 112 return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<E>::type>(e)); 113 } 114 115 } // namespace Detail 116 117 // Some predefined specializations 118 119 template<> 120 struct StringMaker<std::string> { 121 static std::string convert(const std::string& str); 122 }; 123 #ifdef CATCH_CONFIG_WCHAR 124 template<> 125 struct StringMaker<std::wstring> { 126 static std::string convert(const std::wstring& wstr); 127 }; 128 #endif 129 130 template<> 131 struct StringMaker<char const *> { 132 static std::string convert(char const * str); 133 }; 134 template<> 135 struct StringMaker<char *> { 136 static std::string convert(char * str); 137 }; 138 #ifdef CATCH_CONFIG_WCHAR 139 template<> 140 struct StringMaker<wchar_t const *> { 141 static std::string convert(wchar_t const * str); 142 }; 143 template<> 144 struct StringMaker<wchar_t *> { 145 static std::string convert(wchar_t * str); 146 }; 147 #endif 148 149 template<int SZ> 150 struct StringMaker<char[SZ]> { 151 static std::string convert(const char* str) { 152 return ::Catch::Detail::stringify(std::string{ str }); 153 } 154 }; 155 template<int SZ> 156 struct StringMaker<signed char[SZ]> { 157 static std::string convert(const char* str) { 158 return ::Catch::Detail::stringify(std::string{ str }); 159 } 160 }; 161 template<int SZ> 162 struct StringMaker<unsigned char[SZ]> { 163 static std::string convert(const char* str) { 164 return ::Catch::Detail::stringify(std::string{ str }); 165 } 166 }; 167 168 template<> 169 struct StringMaker<int> { 170 static std::string convert(int value); 171 }; 172 template<> 173 struct StringMaker<long> { 174 static std::string convert(long value); 175 }; 176 template<> 177 struct StringMaker<long long> { 178 static std::string convert(long long value); 179 }; 180 template<> 181 struct StringMaker<unsigned int> { 182 static std::string convert(unsigned int value); 183 }; 184 template<> 185 struct StringMaker<unsigned long> { 186 static std::string convert(unsigned long value); 187 }; 188 template<> 189 struct StringMaker<unsigned long long> { 190 static std::string convert(unsigned long long value); 191 }; 192 193 template<> 194 struct StringMaker<bool> { 195 static std::string convert(bool b); 196 }; 197 198 template<> 199 struct StringMaker<char> { 200 static std::string convert(char c); 201 }; 202 template<> 203 struct StringMaker<signed char> { 204 static std::string convert(signed char c); 205 }; 206 template<> 207 struct StringMaker<unsigned char> { 208 static std::string convert(unsigned char c); 209 }; 210 211 template<> 212 struct StringMaker<std::nullptr_t> { 213 static std::string convert(std::nullptr_t); 214 }; 215 216 template<> 217 struct StringMaker<float> { 218 static std::string convert(float value); 219 }; 220 template<> 221 struct StringMaker<double> { 222 static std::string convert(double value); 223 }; 224 225 template <typename T> 226 struct StringMaker<T*> { 227 template <typename U> 228 static std::string convert(U* p) { 229 if (p) { 230 return ::Catch::Detail::rawMemoryToString(p); 231 } else { 232 return "nullptr"; 233 } 234 } 235 }; 236 237 template <typename R, typename C> 238 struct StringMaker<R C::*> { 239 static std::string convert(R C::* p) { 240 if (p) { 241 return ::Catch::Detail::rawMemoryToString(p); 242 } else { 243 return "nullptr"; 244 } 245 } 246 }; 247 248 namespace Detail { 249 template<typename InputIterator> 250 std::string rangeToString(InputIterator first, InputIterator last) { 251 ReusableStringStream rss; 252 rss << "{ "; 253 if (first != last) { 254 rss << ::Catch::Detail::stringify(*first); 255 for (++first; first != last; ++first) 256 rss << ", " << ::Catch::Detail::stringify(*first); 257 } 258 rss << " }"; 259 return rss.str(); 260 } 261 } 262 263 #ifdef __OBJC__ 264 template<> 265 struct StringMaker<NSString*> { 266 static std::string convert(NSString * nsstring) { 267 if (!nsstring) 268 return "nil"; 269 return std::string("@") + [nsstring UTF8String]; 270 } 271 }; 272 template<> 273 struct StringMaker<NSObject*> { 274 static std::string convert(NSObject* nsObject) { 275 return ::Catch::Detail::stringify([nsObject description]); 276 } 277 278 }; 279 namespace Detail { 280 inline std::string stringify( NSString* nsstring ) { 281 return StringMaker<NSString*>::convert( nsstring ); 282 } 283 284 } // namespace Detail 285 #endif // __OBJC__ 286 287 } // namespace Catch 288 289 ////////////////////////////////////////////////////// 290 // Separate std-lib types stringification, so it can be selectively enabled 291 // This means that we do not bring in 292 293 #if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) 294 # define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER 295 # define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER 296 # define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER 297 #endif 298 299 // Separate std::pair specialization 300 #if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) 301 #include <utility> 302 namespace Catch { 303 template<typename T1, typename T2> 304 struct StringMaker<std::pair<T1, T2> > { 305 static std::string convert(const std::pair<T1, T2>& pair) { 306 ReusableStringStream rss; 307 rss << "{ " 308 << ::Catch::Detail::stringify(pair.first) 309 << ", " 310 << ::Catch::Detail::stringify(pair.second) 311 << " }"; 312 return rss.str(); 313 } 314 }; 315 } 316 #endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER 317 318 // Separate std::tuple specialization 319 #if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) 320 #include <tuple> 321 namespace Catch { 322 namespace Detail { 323 template< 324 typename Tuple, 325 std::size_t N = 0, 326 bool = (N < std::tuple_size<Tuple>::value) 327 > 328 struct TupleElementPrinter { 329 static void print(const Tuple& tuple, std::ostream& os) { 330 os << (N ? ", " : " ") 331 << ::Catch::Detail::stringify(std::get<N>(tuple)); 332 TupleElementPrinter<Tuple, N + 1>::print(tuple, os); 333 } 334 }; 335 336 template< 337 typename Tuple, 338 std::size_t N 339 > 340 struct TupleElementPrinter<Tuple, N, false> { 341 static void print(const Tuple&, std::ostream&) {} 342 }; 343 344 } 345 346 347 template<typename ...Types> 348 struct StringMaker<std::tuple<Types...>> { 349 static std::string convert(const std::tuple<Types...>& tuple) { 350 ReusableStringStream rss; 351 rss << '{'; 352 Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get()); 353 rss << " }"; 354 return rss.str(); 355 } 356 }; 357 } 358 #endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER 359 360 namespace Catch { 361 struct not_this_one {}; // Tag type for detecting which begin/ end are being selected 362 363 // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace 364 using std::begin; 365 using std::end; 366 367 not_this_one begin( ... ); 368 not_this_one end( ... ); 369 370 template <typename T> 371 struct is_range { 372 static const bool value = 373 !std::is_same<decltype(begin(std::declval<T>())), not_this_one>::value && 374 !std::is_same<decltype(end(std::declval<T>())), not_this_one>::value; 375 }; 376 377 template<typename Range> 378 std::string rangeToString( Range const& range ) { 379 return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); 380 } 381 382 // Handle vector<bool> specially 383 template<typename Allocator> 384 std::string rangeToString( std::vector<bool, Allocator> const& v ) { 385 ReusableStringStream rss; 386 rss << "{ "; 387 bool first = true; 388 for( bool b : v ) { 389 if( first ) 390 first = false; 391 else 392 rss << ", "; 393 rss << ::Catch::Detail::stringify( b ); 394 } 395 rss << " }"; 396 return rss.str(); 397 } 398 399 template<typename R> 400 struct StringMaker<R, typename std::enable_if<is_range<R>::value && !::Catch::Detail::IsStreamInsertable<R>::value>::type> { 401 static std::string convert( R const& range ) { 402 return rangeToString( range ); 403 } 404 }; 405 406 template <typename T, int SZ> 407 struct StringMaker<T[SZ]> { 408 static std::string convert(T const(&arr)[SZ]) { 409 return rangeToString(arr); 410 } 411 }; 412 413 414 } // namespace Catch 415 416 // Separate std::chrono::duration specialization 417 #if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) 418 #include <ctime> 419 #include <ratio> 420 #include <chrono> 421 422 423 namespace Catch { 424 425 template <class Ratio> 426 struct ratio_string { 427 static std::string symbol(); 428 }; 429 430 template <class Ratio> 431 std::string ratio_string<Ratio>::symbol() { 432 Catch::ReusableStringStream rss; 433 rss << '[' << Ratio::num << '/' 434 << Ratio::den << ']'; 435 return rss.str(); 436 } 437 template <> 438 struct ratio_string<std::atto> { 439 static std::string symbol(); 440 }; 441 template <> 442 struct ratio_string<std::femto> { 443 static std::string symbol(); 444 }; 445 template <> 446 struct ratio_string<std::pico> { 447 static std::string symbol(); 448 }; 449 template <> 450 struct ratio_string<std::nano> { 451 static std::string symbol(); 452 }; 453 template <> 454 struct ratio_string<std::micro> { 455 static std::string symbol(); 456 }; 457 template <> 458 struct ratio_string<std::milli> { 459 static std::string symbol(); 460 }; 461 462 //////////// 463 // std::chrono::duration specializations 464 template<typename Value, typename Ratio> 465 struct StringMaker<std::chrono::duration<Value, Ratio>> { 466 static std::string convert(std::chrono::duration<Value, Ratio> const& duration) { 467 ReusableStringStream rss; 468 rss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's'; 469 return rss.str(); 470 } 471 }; 472 template<typename Value> 473 struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> { 474 static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) { 475 ReusableStringStream rss; 476 rss << duration.count() << " s"; 477 return rss.str(); 478 } 479 }; 480 template<typename Value> 481 struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> { 482 static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) { 483 ReusableStringStream rss; 484 rss << duration.count() << " m"; 485 return rss.str(); 486 } 487 }; 488 template<typename Value> 489 struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> { 490 static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) { 491 ReusableStringStream rss; 492 rss << duration.count() << " h"; 493 return rss.str(); 494 } 495 }; 496 497 //////////// 498 // std::chrono::time_point specialization 499 // Generic time_point cannot be specialized, only std::chrono::time_point<system_clock> 500 template<typename Clock, typename Duration> 501 struct StringMaker<std::chrono::time_point<Clock, Duration>> { 502 static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) { 503 return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; 504 } 505 }; 506 // std::chrono::time_point<system_clock> specialization 507 template<typename Duration> 508 struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> { 509 static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) { 510 auto converted = std::chrono::system_clock::to_time_t(time_point); 511 512 #ifdef _MSC_VER 513 std::tm timeInfo = {}; 514 gmtime_s(&timeInfo, &converted); 515 #else 516 std::tm* timeInfo = std::gmtime(&converted); 517 #endif 518 519 auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); 520 char timeStamp[timeStampSize]; 521 const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; 522 523 #ifdef _MSC_VER 524 std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); 525 #else 526 std::strftime(timeStamp, timeStampSize, fmt, timeInfo); 527 #endif 528 return std::string(timeStamp); 529 } 530 }; 531 } 532 #endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER 533 534 535 #ifdef _MSC_VER 536 #pragma warning(pop) 537 #endif 538 539 #endif // TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED 540