eranpeer / FakeIt

C++ mocking made easy. A simple yet very expressive, headers only library for c++ mocking.
MIT License
1.24k stars 170 forks source link

Formatter could format more types #49

Closed coombez closed 8 years ago

coombez commented 8 years ago

In my project I've replaced your formatter with the following, which allows any type with the standard operator<< defined to be formatted, including my custom types. Just sharing in case you like it. Feel free to use or discard as you please.

template <typename T>
struct Formatter
{
  static auto format(T const &val) -> decltype((std::cout << val), std::string())
  {
    std::ostringstream os;
    os << val;
    return os.str();
  }

  static std::string format(...)
  {
    return "?";
  }
};
eranpeer commented 8 years ago

Thanks, did you try to build the tests project? It doesn't compile. I'll be happy to include your improvement after the test project pass compilation & all tests pass.

1>c:\users\eran\projects\fakeit\include\mockutils\formatter.hpp(20): error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'const A' (or there is no acceptable conversion) 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(498): could be 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(std::basic_streambuf<char,std::char_traits> )' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(478): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(const void )' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(458): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(long double)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(438): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(double)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(418): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(float)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(397): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(unsigned int64)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(377): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(int64)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(356): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(unsigned long)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(336): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(long)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(316): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(unsigned int)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(291): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(int)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(271): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(unsigned short)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(237): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(short)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(217): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(std::_Bool)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(210): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(std::ios_base &(__cdecl )(std::ios_base &))' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(203): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(std::basic_ios<char,std::char_traits> &(__cdecl )(std::basic_ios<char,std::char_traits> &))' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(197): or 'std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(std::basic_ostream<char,std::char_traits> &(__cdecl )(std::basic_ostream<char,std::char_traits> &))' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(699): or 'std::basic_ostream<char,std::char_traits> &std::operator <<<char,std::char_traits>(std::basic_ostream<char,std::char_traits> &,const char )' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(746): or 'std::basic_ostream<char,std::char_traits> &std::operator <<<char,std::char_traits>(std::basic_ostream<char,std::char_traits> &,char)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(784): or 'std::basic_ostream<char,std::char_traits> &std::operator <<std::char_traits(std::basic_ostream<char,std::char_traits> &,const char )' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(831): or 'std::basic_ostream<char,std::char_traits> &std::operator <<std::char_traits(std::basic_ostream<char,std::char_traits> &,char)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(957): or 'std::basic_ostream<char,std::char_traits> &std::operator <<std::char_traits(std::basic_ostream<char,std::char_traits> &,const signed char )' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(964): or 'std::basic_ostream<char,std::char_traits> &std::operator <<std::char_traits(std::basic_ostream<char,std::char_traits> &,signed char)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(971): or 'std::basic_ostream<char,std::char_traits> &std::operator <<std::char_traits(std::basic_ostream<char,std::char_traits> &,const unsigned char *)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(978): or 'std::basic_ostream<char,std::char_traits> &std::operator <<std::char_traits(std::basic_ostream<char,std::char_traits> &,unsigned char)' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(988): or 'std::basic_ostream<char,std::char_traits> &std::operator <<<char,std::char_traits,A>(std::basic_ostream<char,std::char_traits> &&,const _Ty &)' 1> with 1> [ 1> _Ty=A 1> ] 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\ostream(1026): or 'std::basic_ostream<char,std::char_traits> &std::operator <<<char,std::char_traits>(std::basic_ostream<char,std::char_traits> &,const std::error_code &)' 1> while trying to match the argument list '(std::ostream, const A)' 1> c:\users\eran\projects\fakeit\include\mockutils\tupleprinter.hpp(30) : see reference to class template instantiation 'fakeit::Formatter<const A &>' being compiled 1> c:\users\eran\projects\fakeit\include\mockutils\tupleprinter.hpp(29) : while compiling class template member function 'void fakeit::TuplePrinter<const std::tuple<const A &> &,1>::print(std::ostream &,Tuple)' 1> with 1> [ 1> Tuple=const std::tuple<const A &> & 1> ] 1> c:\users\eran\projects\fakeit\include\mockutils\tupleprinter.hpp(43) : see reference to function template instantiation 'void fakeit::TuplePrinter<const std::tuple<const A &> &,1>::print(std::ostream &,Tuple)' being compiled 1> with 1> [ 1> Tuple=const std::tuple<const A &> & 1> ] 1> c:\users\eran\projects\fakeit\include\mockutils\tupleprinter.hpp(43) : see reference to class template instantiation 'fakeit::TuplePrinter<const std::tuple<const A &> &,1>' being compiled 1> c:\users\eran\projects\fakeit\include\fakeit\actualinvocation.hpp(77) : see reference to function template instantiation 'void fakeit::print<const A&>(std::ostream &,const std::tuple<const A &> &)' being compiled 1> c:\users\eran\projects\fakeit\include\fakeit\actualinvocation.hpp(74) : while compiling class template member function 'std::string fakeit::ActualInvocation<const A &>::format(void) const' 1> c:\users\eran\projects\fakeit\include\fakeit\methodmockingcontext.hpp(78) : see reference to class template instantiation 'fakeit::ActualInvocation<const A &>' being compiled 1> c:\users\eran\projects\fakeit\include\fakeit\methodmockingcontext.hpp(185) : see reference to class template instantiation 'fakeit::MethodMockingContext<void,const A &>::Implementation' being compiled 1> c:\users\eran\projects\fakeit\include\fakeit\methodmockingcontext.hpp(184) : while compiling class template member function 'std::string fakeit::MethodMockingContext<void,const A &>::format(void) const' 1> c:\users\eran\projects\fakeit\include\fakeit\methodmockingcontext.hpp(337) : see reference to class template instantiation 'fakeit::MethodMockingContext<void,const A &>' being compiled 1> c:\users\eran\projects\fakeit\tests\verification_tests.cpp(325) : see reference to class template instantiation 'fakeit::MockingContext<void,const A &>' being compiled

coombez commented 8 years ago

I'm not sure of the best way to submit a patch. I'm used to git, but not github, so I'm just pasting it right here... hopefully that will work ok. This passes all tests on GCC. My first implementation did a poor job of trying to use sfinae.

I changed the two specializations of Formatter, but really just defining an operator<< for class 'A' would probably be more convenient now. I don't know if you considered the Formatter part of your interface with the expectation that users would specialize. If so, I've broken this interface with this patch, but that could probably be massaged if necessary.

diff --git a/include/mockutils/Formatter.hpp b/include/mockutils/Formatter.hpp
index 7946712..a048413 100644
--- a/include/mockutils/Formatter.hpp
+++ b/include/mockutils/Formatter.hpp
@@ -10,92 +10,58 @@
 #include <ostream>
 #include <type_traits>
 #include <string>
-#include "mockutils/to_string.hpp"

 namespace fakeit {

-    template<class T>
-    struct Formatter {
-        static std::string format(const T &val) {
-            if (std::is_const<T>::value)
-                return Formatter<typename std::remove_const<T>::type>::format(val);
-            if (std::is_reference<T>::value)
-                return Formatter<typename std::remove_reference<T>::type>::format(val);
-            if (std::is_volatile<T>::value)
-                return Formatter<typename std::remove_volatile<T>::type>::format(val);
+template <typename T>
+class is_ostreamable
+{
+  struct no {};
+  template <typename T1>
+  static auto test(std::ostream &s, const T1 &t) -> decltype(s << t);
+  static no test(...);
+public:
+  static const bool value = std::is_same<decltype(test(*(std::ostream *)nullptr,
+      std::declval<T>())),std::ostream &>::value;
+};

-            return {"?"};
-        }
-    };

-    template<>
-    struct Formatter<bool> {
-        static std::string format(const bool &val) {
-            return val ? "true" : "false";
-        }
-    };
+template <typename T, bool streamable>
+struct FormatterImpl
+{
+  static std::string format(T const &)
+  {
+    return "?";
+  }
+};

-    template<>
-    struct Formatter<int> {
-        static std::string format(const int &val) {
-            return fakeit::to_string(val);
-        }
-    };
+template <typename T>
+struct FormatterImpl<T, true>
+{
+  static std::string format(T const &val)
+  {
+    std::ostringstream os;
+    os << val;
+    return os.str();
+  }
+};

-    template<>
-    struct Formatter<unsigned int> {
-        static std::string format(const unsigned int &val) {
-            return fakeit::to_string(val);
-        }
-    };
+template <>
+struct FormatterImpl<bool, true>
+{
+  static std::string format(bool const &val)
+  {
+    return val ? "true" : "false";
+  }
+};

-    template<>
-    struct Formatter<long> {
-        static std::string format(const long &val) {
-            return fakeit::to_string(val);
-        }
-    };
-
-    template<>
-    struct Formatter<unsigned long> {
-        static std::string format(const unsigned long &val) {
-            return fakeit::to_string(val);
-        }
-    };
-
-    template<>
-    struct Formatter<long long> {
-        static std::string format(const long long &val) {
-            return fakeit::to_string(val);
-        }
-    };
-
-    template<>
-    struct Formatter<unsigned long long> {
-        static std::string format(const unsigned long long &val) {
-            return fakeit::to_string(val);
-        }
-    };
-
-    template<>
-    struct Formatter<double> {
-        static std::string format(const double &val) {
-            return fakeit::to_string(val);
-        }
-    };
-
-    template<>
-    struct Formatter<long double> {
-        static std::string format(const long double &val) {
-            return fakeit::to_string(val);
-        }
-    };
-
-    template<>
-    struct Formatter<float> {
-        static std::string format(const float &val) {
-            return fakeit::to_string(val);
-        }
-    };
+template <typename T>
+using Formatter = FormatterImpl<
+  typename std::remove_volatile<
+    typename std::remove_const<
+      typename std::remove_reference<T>::type
+      >::type
+    >::type, is_ostreamable<T>::value
+  >;

 }
diff --git a/tests/default_event_formatting_tests.cpp b/tests/default_event_formatting_tests.cpp
index 3ad2728..be55865 100644
--- a/tests/default_event_formatting_tests.cpp
+++ b/tests/default_event_formatting_tests.cpp
@@ -217,7 +217,7 @@ struct DefaultEventFormatting: tpunit::TestFixture {
            expectedMsg += "Actual matches  : 1\n";
            expectedMsg += "Actual sequence : total of 1 actual invocations:\n";
            //expectedMsg += "  mock.all_types(?, true, 1, 1, 1, 1, 1, 1, 1.000000, 1.000000)";
-           expectedMsg += "  mock.all_types(?, true, 1, 1, 1, 1, 0, 0)";
+           expectedMsg += "  mock.all_types(a, true, 1, 1, 1, 1, 0, 0)";

            std::string actualMsg {to_string(e)};
            std::cout << actualMsg;
@@ -240,7 +240,7 @@ struct DefaultEventFormatting: tpunit::TestFixture {
             std::string expectedMsg{ formatLineNumner("test file", 1) };
             expectedMsg += ": Verification error\n";
            //expectedMsg += "Expected pattern: mock.all_types('a', true, 1, 1, 1, 1, 1.000000)\n";
-           expectedMsg += "Expected pattern: mock.all_types(?, true, 1, 1, 1, 1, 0, 0)\n";
+           expectedMsg += "Expected pattern: mock.all_types(a, true, 1, 1, 1, 1, 0, 0)\n";
            expectedMsg += "Expected matches: exactly 2\n";
            expectedMsg += "Actual matches  : 0\n";
            expectedMsg += "Actual sequence : total of 0 actual invocations.";
diff --git a/tests/verification_errors_tests.cpp b/tests/verification_errors_tests.cpp
index 099898a..df328c5 100644
--- a/tests/verification_errors_tests.cpp
+++ b/tests/verification_errors_tests.cpp
@@ -26,7 +26,7 @@ struct A {
 };

 namespace fakeit {
-template<> struct Formatter<A> {
+template<> struct FormatterImpl<A, false> {
    static std::string format(const A&) {
        return {"a"};
    }
diff --git a/tests/verification_tests.cpp b/tests/verification_tests.cpp
index a20d5fa..9773400 100644
--- a/tests/verification_tests.cpp
+++ b/tests/verification_tests.cpp
@@ -28,7 +28,7 @@ struct A {
 };

 namespace fakeit {
-template<> struct Formatter<A> {
+template<> struct FormatterImpl<A, false> {
    static std::string format(const A&) {
        return {"a"};
    }
eranpeer commented 8 years ago

Thanks you for the suggestion and the solution. I used most of your code but changed it a little in order to keep the backwards compatibility. You can pull the latest version. Enjoy!