diff options
author | dmichael@chromium.org <dmichael@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-24 01:10:23 +0000 |
---|---|---|
committer | dmichael@chromium.org <dmichael@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-24 01:10:23 +0000 |
commit | 6d8528630b4841afa5a9b0c09be17d9f74d779db (patch) | |
tree | a7d2a4d2eec7887003e1eed884ff6e760aa4d0a1 /ppapi/tests/test_case.h | |
parent | 71c10c5ae000e72154770f4e467928a15a05297a (diff) | |
download | chromium_src-6d8528630b4841afa5a9b0c09be17d9f74d779db.zip chromium_src-6d8528630b4841afa5a9b0c09be17d9f74d779db.tar.gz chromium_src-6d8528630b4841afa5a9b0c09be17d9f74d779db.tar.bz2 |
PPAPI: Make ASSERT macros print values of operands
With this patch, if one of our ASSERT macros fails, the error
string will contain the expected and actual values.
I made a browser test for it. I would prefer a
unit test, but I didn't want to have to mix test_case.h with
our unit tests.
BUG=None
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=246129
Review URL: https://codereview.chromium.org/123463007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@246757 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ppapi/tests/test_case.h')
-rw-r--r-- | ppapi/tests/test_case.h | 369 |
1 files changed, 358 insertions, 11 deletions
diff --git a/ppapi/tests/test_case.h b/ppapi/tests/test_case.h index 0a96c9b..948d2b7 100644 --- a/ppapi/tests/test_case.h +++ b/ppapi/tests/test_case.h @@ -9,6 +9,7 @@ #include <limits> #include <map> #include <set> +#include <sstream> #include <string> #include "ppapi/c/pp_resource.h" @@ -273,6 +274,326 @@ class TestCaseFactory { static TestCaseFactory* head_; }; +namespace internal { + +// The internal namespace contains implementation details that are used by +// the ASSERT macros. + +// This base class provides a ToString that works for classes that can be +// converted to a string using std::stringstream. Later, we'll do +// specializations for types that we know will work with this approach. +template <class T> +struct StringinatorBase { + static std::string ToString(const T& value) { + std::stringstream stream; + stream << value; + return stream.str(); + } + protected: + // Not implemented, do not use. + // Note, these are protected because Windows complains if I make these private + // and then inherit StringinatorBase (even though they're never used). + StringinatorBase(); + ~StringinatorBase(); +}; + +// This default class template is for types that we don't recognize as +// something we can convert into a string using stringstream. Types that we +// know *can* be turned to a string should have specializations below. +template <class T> +struct Stringinator { + static std::string ToString(const T& value) { + return std::string(); + } + private: + // Not implemented, do not use. + Stringinator(); + ~Stringinator(); +}; + +// Define some full specializations for types that can just use stringstream. +#define DEFINE_STRINGINATOR_FOR_TYPE(type) \ +template <> \ +struct Stringinator<type> : public StringinatorBase<type> {}; +DEFINE_STRINGINATOR_FOR_TYPE(int32_t); +DEFINE_STRINGINATOR_FOR_TYPE(uint32_t); +DEFINE_STRINGINATOR_FOR_TYPE(int64_t); +DEFINE_STRINGINATOR_FOR_TYPE(uint64_t); +DEFINE_STRINGINATOR_FOR_TYPE(float); +DEFINE_STRINGINATOR_FOR_TYPE(double); +DEFINE_STRINGINATOR_FOR_TYPE(bool); +DEFINE_STRINGINATOR_FOR_TYPE(std::string); +#undef DEFINE_STRINGINATOR_FOR_TYPE + +template <class T> +std::string ToString(const T& param) { + return Stringinator<T>::ToString(param); +} + +// This overload is necessary to allow enum values (such as those from +// pp_errors.h, including PP_OK) to work. They won't automatically convert to +// an integral type to instantiate the above function template. +inline std::string ToString(int32_t param) { + return Stringinator<int32_t>::ToString(param); +} + +inline std::string ToString(const char* c_string) { + return std::string(c_string); +} + +// This overload deals with pointers. +template <class T> +std::string ToString(const T* ptr) { + uintptr_t ptr_val = reinterpret_cast<uintptr_t>(ptr); + std::stringstream stream; + stream << ptr_val; + return stream.str(); +} + +// ComparisonHelper classes wrap the left-hand parameter of a binary comparison +// ASSERT. The correct class gets chosen based on whether or not it's a NULL or +// 0 literal. If it is a NULL/0 literal, we use NullLiteralComparisonHelper. +// For all other parameters, we use ComparisonHelper. There's also a +// specialization of ComparisonHelper for int below (see below for why +// that is.) +// +// ComparisonHelper does two things for the left param: +// 1) Provides all the appropriate CompareXX functions (CompareEQ, etc). +// 2) Provides ToString. +template <class T> +struct ComparisonHelper { + explicit ComparisonHelper(const T& param) : value(param) {} + template <class U> + bool CompareEQ(const U& right) const { + return value == right; + } + template <class U> + bool CompareNE(const U& right) const { + return value != right; + } + template <class U> + bool CompareLT(const U& right) const { + return value < right; + } + template <class U> + bool CompareGT(const U& right) const { + return value > right; + } + template <class U> + bool CompareLE(const U& right) const { + return value <= right; + } + template <class U> + bool CompareGE(const U& right) const { + return value >= right; + } + std::string ToString() const { + return internal::ToString(value); + } + const T& value; +}; + +// Used for NULL or 0. +struct NullLiteralComparisonHelper { + NullLiteralComparisonHelper() : value(0) {} + template <class U> + bool CompareEQ(const U& right) const { + return 0 == right; + } + template <class U> + bool CompareNE(const U& right) const { + return 0 != right; + } + template <class U> + bool CompareLT(const U& right) const { + return 0 < right; + } + template <class U> + bool CompareGT(const U& right) const { + return 0 > right; + } + template <class U> + bool CompareLE(const U& right) const { + return 0 <= right; + } + template <class U> + bool CompareGE(const U& right) const { + return 0 >= right; + } + std::string ToString() const { + return std::string("0"); + } + const int value; +}; + +// This class makes it safe to use an integer literal (like 5, or 123) when +// comparing with an unsigned. For example: +// ASSERT_EQ(1, some_vector.size()); +// We do a lot of those comparisons, so this makes it easy to get it right +// (rather than forcing assertions to use unsigned literals like 5u or 123u). +// +// This is slightly risky; we're static_casting an int to whatever's on the +// right. If the left value is negative and the right hand side is a large +// unsigned value, it's possible that the comparison will succeed when maybe +// it shouldn't have. +// TODO(dmichael): It should be possible to fix this and upgrade int32_t and +// uint32_t to int64_t for the comparison, and make any unsafe +// comparisons into compile errors. +template <> +struct ComparisonHelper<int> { + explicit ComparisonHelper(int param) : value(param) {} + template <class U> + bool CompareEQ(const U& right) const { + return static_cast<U>(value) == right; + } + template <class U> + bool CompareNE(const U& right) const { + return static_cast<U>(value) != right; + } + template <class U> + bool CompareLT(const U& right) const { + return static_cast<U>(value) < right; + } + template <class U> + bool CompareGT(const U& right) const { + return static_cast<U>(value) > right; + } + template <class U> + bool CompareLE(const U& right) const { + return static_cast<U>(value) <= right; + } + template <class U> + bool CompareGE(const U& right) const { + return static_cast<U>(value) >= right; + } + std::string ToString() const { + return internal::ToString(value); + } + const int value; + private: +}; + +// The default is for the case there the parameter is *not* a NULL or 0 literal. +template <bool is_null_literal> +struct ParameterWrapper { + template <class T> + static ComparisonHelper<T> WrapValue(const T& value) { + return ComparisonHelper<T>(value); + } + // This overload is so that we can deal with values from anonymous enums, + // like the one in pp_errors.h. The function template above won't be + // considered a match by the compiler. + static ComparisonHelper<int> WrapValue(int value) { + return ComparisonHelper<int>(value); + } +}; + +// The parameter to WrapValue *is* a NULL or 0 literal. +template <> +struct ParameterWrapper<true> { + // We just use "..." and ignore the parameter. This sidesteps some problems we + // would run in to (not all compilers have the same set of constraints). + // - We can't use a pointer type, because int and enums won't convert. + // - We can't use an integral type, because pointers won't convert. + // - We can't overload, because it will sometimes be ambiguous. + // - We can't templatize and deduce the parameter. Some compilers will deduce + // int for NULL, and then refuse to convert NULL to an int. + // + // We know in this case that the value is 0, so there's no need to capture the + // value. We also know it's a fundamental type, so it's safe to pass to "...". + // (It's illegal to pass non-POD types to ...). + static NullLiteralComparisonHelper WrapValue(...) { + return NullLiteralComparisonHelper(); + } +}; + +// IS_NULL_LITERAL(type) is a little template metaprogramming for determining +// if a type is a null or zero literal (NULL or 0 or a constant that evaluates +// to one of those). +// The idea is that for NULL or 0, any pointer type is always a better match +// than "...". But no other pointer types or literals should convert +// automatically to InternalDummyClass. +struct InternalDummyClass {}; +char TestNullLiteral(const InternalDummyClass*); +struct BiggerThanChar { char dummy[2]; }; +BiggerThanChar TestNullLiteral(...); +// If the compiler chooses the overload of TestNullLiteral which returns char, +// then we know the value converts automatically to InternalDummyClass*, which +// should only be true of NULL and 0 constants. +#define IS_NULL_LITERAL(a) sizeof(internal::TestNullLiteral(a)) == sizeof(char) + +template <class T, class U> +static std::string MakeBinaryComparisonFailureMessage( + const char* comparator, + const T& left, + const U& right, + const char* left_precompiler_string, + const char* right_precompiler_string, + const char* file_name, + int line_number) { + std::string error_msg = + std::string("Failed ASSERT_") + comparator + "(" + + left_precompiler_string + ", " + right_precompiler_string + ")"; + std::string left_string(left.ToString()); + std::string right_string(ToString(right)); + if (!left_string.empty()) + error_msg += " Left: (" + left_string + ")"; + + if (!right_string.empty()) + error_msg += " Right: (" + right_string + ")"; + + return TestCase::MakeFailureMessage(file_name, line_number, + error_msg.c_str()); +} + +// The Comparison function templates allow us to pass the parameter for +// ASSERT macros below and have them be evaluated only once. This is important +// for cases where the parameter might be an expression with side-effects, like +// a function call. +#define DEFINE_COMPARE_FUNCTION(comparator_name) \ +template <class T, class U> \ +std::string Compare ## comparator_name ( \ + const T& left, \ + const U& right, \ + const char* left_precompiler_string, \ + const char* right_precompiler_string, \ + const char* file_name, \ + int line_num) { \ + if (!(left.Compare##comparator_name(right))) { \ + return MakeBinaryComparisonFailureMessage(#comparator_name, \ + left, \ + right, \ + left_precompiler_string, \ + right_precompiler_string, \ + file_name, \ + line_num); \ + } \ + return std::string(); \ +} +DEFINE_COMPARE_FUNCTION(EQ) +DEFINE_COMPARE_FUNCTION(NE) +DEFINE_COMPARE_FUNCTION(LT) +DEFINE_COMPARE_FUNCTION(LE) +DEFINE_COMPARE_FUNCTION(GT) +DEFINE_COMPARE_FUNCTION(GE) +#undef DEFINE_COMPARE_FUNCTION +inline std::string CompareDoubleEq(ComparisonHelper<double> left, + double right, + const char* left_precompiler_string, + const char* right_precompiler_string, + const char* file_name, + int linu_num) { + if (!(std::fabs(left.value - right) <= + std::numeric_limits<double>::epsilon())) { + return MakeBinaryComparisonFailureMessage( + "~=", left, right, left_precompiler_string, right_precompiler_string, + __FILE__, __LINE__); + } + return std::string(); +} + +} // namespace internal + // Use the REGISTER_TEST_CASE macro in your TestCase implementation file to // register your TestCase. If your test is named TestFoo, then add the // following to test_foo.cc: @@ -373,22 +694,48 @@ class TestCaseFactory { return MakeFailureMessage(__FILE__, __LINE__, #cmd); \ } while (false) #define ASSERT_FALSE(cmd) ASSERT_TRUE(!(cmd)) -#define ASSERT_EQ(a, b) ASSERT_TRUE((a) == (b)) -#define ASSERT_NE(a, b) ASSERT_TRUE((a) != (b)) -#define ASSERT_LT(a, b) ASSERT_TRUE((a) < (b)) -#define ASSERT_LE(a, b) ASSERT_TRUE((a) <= (b)) -#define ASSERT_GT(a, b) ASSERT_TRUE((a) > (b)) -#define ASSERT_GE(a, b) ASSERT_TRUE((a) >= (b)) - -#define ASSERT_DOUBLE_EQ(a, b) ASSERT_TRUE( \ - std::fabs((a)-(b)) <= std::numeric_limits<double>::epsilon()) - +#define COMPARE_BINARY_INTERNAL(comparison_type, a, b) \ + internal::Compare##comparison_type( \ + internal::ParameterWrapper<IS_NULL_LITERAL(a)>::WrapValue(a), \ + (b), \ + #a, \ + #b, \ + __FILE__, \ + __LINE__) +#define ASSERT_BINARY_INTERNAL(comparison_type, a, b) \ +do { \ + std::string internal_assert_result_string = \ + COMPARE_BINARY_INTERNAL(comparison_type, a, b); \ + if (!internal_assert_result_string.empty()) { \ + return internal_assert_result_string; \ + } \ +} while(false) +#define ASSERT_EQ(a, b) ASSERT_BINARY_INTERNAL(EQ, a, b) +#define ASSERT_NE(a, b) ASSERT_BINARY_INTERNAL(NE, a, b) +#define ASSERT_LT(a, b) ASSERT_BINARY_INTERNAL(LT, a, b) +#define ASSERT_LE(a, b) ASSERT_BINARY_INTERNAL(LE, a, b) +#define ASSERT_GT(a, b) ASSERT_BINARY_INTERNAL(GT, a, b) +#define ASSERT_GE(a, b) ASSERT_BINARY_INTERNAL(GE, a, b) +#define ASSERT_DOUBLE_EQ(a, b) \ +do { \ + std::string internal_assert_result_string = \ + internal::CompareDoubleEq( \ + internal::ParameterWrapper<IS_NULL_LITERAL(a)>::WrapValue(a), \ + (b), \ + #a, \ + #b, \ + __FILE__, \ + __LINE__); \ + if (!internal_assert_result_string.empty()) { \ + return internal_assert_result_string; \ + } \ +} while(false) // Runs |function| as a subtest and asserts that it has passed. #define ASSERT_SUBTEST_SUCCESS(function) \ do { \ std::string result = (function); \ if (!result.empty()) \ - return result; \ + return TestCase::MakeFailureMessage(__FILE__, __LINE__, result.c_str()); \ } while (false) #define PASS() return std::string() |