diff options
author | Andy Schwerin <schwerin@mongodb.com> | 2014-10-09 10:28:20 -0400 |
---|---|---|
committer | Andy Schwerin <schwerin@mongodb.com> | 2014-10-09 10:28:20 -0400 |
commit | 1c9d735cc6d439eb1b5e0b8f92c81d0b88b53a53 (patch) | |
tree | 728901d9142c9f09f27dffc54dc5b1f8e520da50 /src/mongo/unittest | |
parent | 2eaa2d50def6702f5df83ec5ab97e92c3dc8b476 (diff) | |
download | mongo-1c9d735cc6d439eb1b5e0b8f92c81d0b88b53a53.tar.gz |
SERVER-15567 Allow streaming into unittest assertions.
Diffstat (limited to 'src/mongo/unittest')
-rw-r--r-- | src/mongo/unittest/unittest-inl.h | 9 | ||||
-rw-r--r-- | src/mongo/unittest/unittest.cpp | 40 | ||||
-rw-r--r-- | src/mongo/unittest/unittest.h | 223 | ||||
-rw-r--r-- | src/mongo/unittest/unittest_test.cpp | 14 |
4 files changed, 119 insertions, 167 deletions
diff --git a/src/mongo/unittest/unittest-inl.h b/src/mongo/unittest/unittest-inl.h index cdd2b0cbaa0..0ad891506a8 100644 --- a/src/mongo/unittest/unittest-inl.h +++ b/src/mongo/unittest/unittest-inl.h @@ -37,14 +37,5 @@ namespace mongo { Suite::getSuite(suiteName)->add<T>(testName); } - template<typename A, typename B> - std::string ComparisonAssertion::getComparisonFailureMessage(const std::string &theOperator, - const A &a, const B &b) { - std::ostringstream os; - os << "Expected " << _aexp << " " << theOperator << " " << _bexp - << " (" << a << " " << theOperator << " " << b << ")"; - return os.str(); - } - } // namespace mongo } // namespace unittest diff --git a/src/mongo/unittest/unittest.cpp b/src/mongo/unittest/unittest.cpp index feb9a2aeb05..64f63fa4cc2 100644 --- a/src/mongo/unittest/unittest.cpp +++ b/src/mongo/unittest/unittest.cpp @@ -371,38 +371,42 @@ namespace { TestAssertionFailure::TestAssertionFailure(const std::string& file, unsigned line, const std::string& message) - : _exception(file, line, message) {} + : _exception(file, line, message), _enabled(false) {} + + TestAssertionFailure::TestAssertionFailure(const TestAssertionFailure& other) : + _exception(other._exception), _enabled(false) { + + invariant(!other._enabled); + } + + TestAssertionFailure& TestAssertionFailure::operator=(const TestAssertionFailure& other) { + invariant(!_enabled); + invariant(!other._enabled); + _exception = other._exception; + return *this; + } TestAssertionFailure::~TestAssertionFailure() #if __cplusplus >= 201103 noexcept(false) #endif { - if (!_stream.str().empty()) + if (!_enabled) { + invariant(_stream.str().empty()); + return; + } + if (!_stream.str().empty()) { _exception.setMessage(_exception.getMessage() + " " + _stream.str()); + } throw _exception; } std::ostream& TestAssertionFailure::stream() { + invariant(!_enabled); + _enabled = true; return _stream; } - TestAssertion::TestAssertion( const char* file, unsigned line ) - : _file( file ), _line( line ) { - - ++Result::cur->_asserts; - } - - TestAssertion::~TestAssertion() {} - - void TestAssertion::fail( const std::string& message ) const { - throw TestAssertionFailureException( _file, _line, message ); - } - - ComparisonAssertion::ComparisonAssertion( const char* aexp, const char* bexp, - const char* file, unsigned line ) - : TestAssertion( file, line ), _aexp( aexp ), _bexp( bexp ) {} - std::vector<std::string> getAllSuiteNames() { std::vector<std::string> result; for (SuiteMap::const_iterator i = _allSuites().begin(); i != _allSuites().end(); ++i) { diff --git a/src/mongo/unittest/unittest.h b/src/mongo/unittest/unittest.h index a7a1c1a9b60..374b6674e90 100644 --- a/src/mongo/unittest/unittest.h +++ b/src/mongo/unittest/unittest.h @@ -54,54 +54,61 @@ /** * Fail unconditionally, reporting the given message. */ -#define FAIL(MESSAGE) ::mongo::unittest::TestAssertion( __FILE__ , __LINE__ ).fail( (MESSAGE) ) +#define FAIL(MESSAGE) ::mongo::unittest::TestAssertionFailure(__FILE__, __LINE__, MESSAGE).stream() /** * Fails unless "EXPRESSION" is true. */ -#define ASSERT_TRUE(EXPRESSION) if (!(EXPRESSION)) ::mongo::unittest::TestAssertionFailure(__FILE__, __LINE__, "Expected: " #EXPRESSION).stream() +#define ASSERT_TRUE(EXPRESSION) if (!(EXPRESSION)) FAIL("Expected: " #EXPRESSION) #define ASSERT(EXPRESSION) ASSERT_TRUE(EXPRESSION) /** - * Assert that a Status code is OK. + * Fails if "EXPRESSION" is true. */ -#define ASSERT_OK(EXPRESSION) ASSERT_EQUALS(::mongo::Status::OK(), (EXPRESSION)) +#define ASSERT_FALSE(EXPRESSION) ASSERT(!(EXPRESSION)) /** - * Assert that a status code is anything but OK. + * Asserts that a Status code is OK. */ -#define ASSERT_NOT_OK(EXPRESSION) ASSERT_NOT_EQUALS(::mongo::Status::OK(), (EXPRESSION)) +#define ASSERT_OK(EXPRESSION) ASSERT_EQUALS(::mongo::Status::OK(), (EXPRESSION)) /** - * Fails if "EXPRESSION" is true. + * Asserts that a status code is anything but OK. */ -#define ASSERT_FALSE(EXPRESSION) if (EXPRESSION) \ -::mongo::unittest::TestAssertionFailure(__FILE__, __LINE__, "Expected: !(" #EXPRESSION ")").stream() +#define ASSERT_NOT_OK(EXPRESSION) ASSERT_NOT_EQUALS(::mongo::Status::OK(), (EXPRESSION)) /* * Binary comparison assertions. */ -#define ASSERT_EQUALS(a,b) _ASSERT_COMPARISON(Equal, a, b) -#define ASSERT_NOT_EQUALS(a,b) _ASSERT_COMPARISON(NotEqual, a, b) -#define ASSERT_LESS_THAN(a, b) _ASSERT_COMPARISON(LessThan, a, b) -#define ASSERT_NOT_LESS_THAN(a, b) _ASSERT_COMPARISON(NotLessThan, a, b) -#define ASSERT_GREATER_THAN(a, b) _ASSERT_COMPARISON(GreaterThan, a, b) -#define ASSERT_NOT_GREATER_THAN(a, b) _ASSERT_COMPARISON(NotGreaterThan, a, b) -#define ASSERT_LESS_THAN_OR_EQUALS(a, b) ASSERT_NOT_GREATER_THAN(a, b) -#define ASSERT_GREATER_THAN_OR_EQUALS(a, b) ASSERT_NOT_LESS_THAN(a, b) +#define ASSERT_EQUALS(a,b) ASSERT_EQ(a, b) +#define ASSERT_NOT_EQUALS(a,b) ASSERT_NE(a, b) +#define ASSERT_LESS_THAN(a, b) ASSERT_LT(a, b) +#define ASSERT_NOT_LESS_THAN(a, b) ASSERT_GTE(a, b) +#define ASSERT_GREATER_THAN(a, b) ASSERT_GT(a, b) +#define ASSERT_NOT_GREATER_THAN(a, b) ASSERT_LTE(a, b) +#define ASSERT_LESS_THAN_OR_EQUALS(a, b) ASSERT_LTE(a, b) +#define ASSERT_GREATER_THAN_OR_EQUALS(a, b) ASSERT_GTE(a, b) + +#define ASSERT_EQ(a,b) _ASSERT_COMPARISON(EQ, a, b) +#define ASSERT_NE(a,b) _ASSERT_COMPARISON(NE, a, b) +#define ASSERT_LT(a, b) _ASSERT_COMPARISON(LT, a, b) +#define ASSERT_LTE(a, b) _ASSERT_COMPARISON(LTE, a, b) +#define ASSERT_GT(a, b) _ASSERT_COMPARISON(GT, a, b) +#define ASSERT_GTE(a, b) _ASSERT_COMPARISON(GTE, a, b) /** * Binary comparison utility macro. Do not use directly. */ -#define _ASSERT_COMPARISON(COMPARISON, a, b) ::mongo::unittest::ComparisonAssertion( \ - #a, #b , __FILE__ , __LINE__ ).assert##COMPARISON( (a), (b) ) +#define _ASSERT_COMPARISON(COMPARISON, a, b) \ + if (::mongo::unittest::ComparisonAssertion_##COMPARISON ca = ::mongo::unittest::ComparisonAssertion_##COMPARISON(__FILE__, __LINE__, #a, #b, a, b)) \ + ca.failure().stream() /** * Approximate equality assertion. Useful for comparisons on limited precision floating point * values. */ -#define ASSERT_APPROX_EQUAL(a,b,ABSOLUTE_ERR) ::mongo::unittest::assertApproxEqual( \ - #a, #b, a, b, ABSOLUTE_ERR, __FILE__, __LINE__) +#define ASSERT_APPROX_EQUAL(a, b, ABSOLUTE_ERR) \ + ASSERT_LTE(std::abs((a) - (b)), ABSOLUTE_ERR) /** * Verify that the evaluation of "EXPRESSION" throws an exception of type EXCEPTION_TYPE. @@ -110,8 +117,8 @@ * of a subtype of "EXCEPTION_TYPE", the test is considered a failure and further evaluation * halts. */ -#define ASSERT_THROWS(EXPRESSION, EXCEPTION_TYPE) \ - ASSERT_THROWS_PRED(EXPRESSION, \ +#define ASSERT_THROWS(STATEMENT, EXCEPTION_TYPE) \ + ASSERT_THROWS_PRED(STATEMENT, \ EXCEPTION_TYPE, \ ::mongo::stdx::bind(::mongo::unittest::alwaysTrue)) @@ -119,8 +126,8 @@ * Behaves like ASSERT_THROWS, above, but also fails if calling what() on the thrown exception * does not return a string equal to EXPECTED_WHAT. */ -#define ASSERT_THROWS_WHAT(EXPRESSION, EXCEPTION_TYPE, EXPECTED_WHAT) \ - ASSERT_THROWS_PRED(EXPRESSION, \ +#define ASSERT_THROWS_WHAT(STATEMENT, EXCEPTION_TYPE, EXPECTED_WHAT) \ + ASSERT_THROWS_PRED(STATEMENT, \ EXCEPTION_TYPE, \ ::mongo::stdx::bind(std::equal_to<std::string>(), (EXPECTED_WHAT), \ ::mongo::stdx::bind(&EXCEPTION_TYPE::what, \ @@ -130,20 +137,19 @@ * Behaves like ASSERT_THROWS, above, but also fails if PREDICATE(ex) for the throw exception, ex, * is false. */ -#define ASSERT_THROWS_PRED(EXPRESSION, EXCEPTION_TYPE, PREDICATE) do { \ - ::mongo::unittest::TestAssertion _testAssertion( __FILE__, __LINE__ ); \ +#define ASSERT_THROWS_PRED(STATEMENT, EXCEPTION_TYPE, PREDICATE) do { \ try { \ - EXPRESSION; \ - _testAssertion.fail("Expected expression " #EXPRESSION \ - " to throw " #EXCEPTION_TYPE \ - " but it threw nothing."); \ + STATEMENT; \ + FAIL("Expected statement " #STATEMENT \ + " to throw " #EXCEPTION_TYPE \ + " but it threw nothing."); \ } catch (const EXCEPTION_TYPE& ex) { \ if (!(PREDICATE(ex))) { \ - _testAssertion.fail("Expected " #EXPRESSION \ - " to throw an exception of type " \ - #EXCEPTION_TYPE \ - " where " #PREDICATE \ - "(ex) was true, but it was false."); \ + FAIL("Expected " #STATEMENT \ + " to throw an exception of type " \ + #EXCEPTION_TYPE \ + " where " #PREDICATE \ + "(ex) was true, but it was false."); \ } \ } \ } while (false) @@ -403,120 +409,65 @@ namespace mongo { }; class TestAssertionFailure { - MONGO_DISALLOW_COPYING(TestAssertionFailure); public: TestAssertionFailure( const std::string& file, unsigned line, const std::string& message); + TestAssertionFailure(const TestAssertionFailure& other); #if __cplusplus < 201103 ~TestAssertionFailure(); #else ~TestAssertionFailure() noexcept(false); #endif + + TestAssertionFailure& operator=(const TestAssertionFailure& other); + std::ostream& stream(); private: TestAssertionFailureException _exception; std::ostringstream _stream; + bool _enabled; }; - /** - * Object representing an assertion about some condition. - */ - class TestAssertion : private boost::noncopyable { - - public: - /** - * file std::string must stay in scope and remain unchanged for the lifetime - * of the TestAssertion object. - */ - TestAssertion( const char* file, unsigned line ); - ~TestAssertion(); - - MONGO_COMPILER_NORETURN void fail( const std::string& message) const; - void failIf( bool flag, const std::string &message ) const { - if ( flag ) fail( message ); - } - - private: - const char* _file; - const unsigned _line; - }; - - /** - * Specialization of TestAssertion for binary comparisons. - */ - class ComparisonAssertion : private TestAssertion { - public: - /** - * All char* arguments must stay in scope and remain unchanged for the lifetime - * of the ComparisonAssertion object. - */ - ComparisonAssertion( const char* aexp , const char* bexp , - const char* file , unsigned line ); - - template<typename A,typename B> - void assertEqual( const A& a , const B& b ) { - if ( a == b ) - return; - fail(getComparisonFailureMessage("==", a, b)); - } - - template<typename A,typename B> - void assertNotEqual( const A& a , const B& b ) { - if ( a != b ) - return; - fail(getComparisonFailureMessage("!=", a, b)); - } - - template<typename A,typename B> - void assertLessThan( const A& a , const B& b ) { - if ( a < b ) - return; - fail(getComparisonFailureMessage("<", a, b)); - } - - template<typename A,typename B> - void assertNotLessThan( const A& a , const B& b ) { - if ( a >= b ) - return; - fail(getComparisonFailureMessage(">=", a, b)); - } - - template<typename A,typename B> - void assertGreaterThan( const A& a , const B& b ) { - if ( a > b ) - return; - fail(getComparisonFailureMessage(">", a, b)); - } - - template<typename A,typename B> - void assertNotGreaterThan( const A& a , const B& b ) { - if ( a <= b ) - return; - fail(getComparisonFailureMessage("<=", a, b)); - } - - private: - template< typename A, typename B> - std::string getComparisonFailureMessage(const std::string &theOperator, - const A& a, const B& b); - - const char* _aexp; - const char* _bexp; - }; - - /** - * Helper for ASSERT_APPROX_EQUAL to ensure that the arguments are evaluated only once. - */ - template < typename A, typename B, typename ABSOLUTE_ERR > - inline void assertApproxEqual(const char* aexp, const char* bexp, - const A& a, const B& b, const ABSOLUTE_ERR& absoluteErr, - const char* file , unsigned line) { - if (std::abs(a - b) <= absoluteErr) - return; - TestAssertion(file, line).fail(mongoutils::str::stream() - << "Expected " << aexp << " and " << bexp << " to be within " << absoluteErr - << " of each other ((" << a << ") - (" << b << ") = " << (a - b) << ")"); - } +#define DECLARE_COMPARISON_ASSERTION(NAME, OPERATOR) \ + class ComparisonAssertion_##NAME { \ + typedef void (ComparisonAssertion_##NAME::*bool_type)() const; \ + public: \ + template <typename A, typename B> \ + ComparisonAssertion_##NAME( \ + const std::string& theFile, \ + unsigned theLine, \ + const StringData& aExpression, \ + const StringData& bExpression, \ + const A& a, \ + const B& b) { \ + if (a OPERATOR b) { \ + return; \ + } \ + std::ostringstream os; \ + os << "Expected " << \ + aExpression << " " #OPERATOR " " << bExpression << \ + " (" << a << " " #OPERATOR " " << b << ")"; \ + _assertion.reset(new TestAssertionFailure( \ + theFile, \ + theLine, \ + os.str())); \ + } \ + operator bool_type() const { \ + return _assertion.get() ? &ComparisonAssertion_##NAME::comparison_failed : NULL; \ + } \ + TestAssertionFailure failure() { return *_assertion; } \ + private: \ + void comparison_failed() const {} \ + boost::shared_ptr<TestAssertionFailure> _assertion; \ + } + +DECLARE_COMPARISON_ASSERTION(EQ, ==); +DECLARE_COMPARISON_ASSERTION(NE, !=); +DECLARE_COMPARISON_ASSERTION(LT, <); +DECLARE_COMPARISON_ASSERTION(LTE, <=); +DECLARE_COMPARISON_ASSERTION(GT, >); +DECLARE_COMPARISON_ASSERTION(GTE, >=); +#undef DECLARE_COMPARISON_ASSERTION /** * Get the value out of a StatusWith<T>, or throw an exception if it is not OK. diff --git a/src/mongo/unittest/unittest_test.cpp b/src/mongo/unittest/unittest_test.cpp index 4e62c47761c..7c557a92b56 100644 --- a/src/mongo/unittest/unittest_test.cpp +++ b/src/mongo/unittest/unittest_test.cpp @@ -46,12 +46,12 @@ namespace { return value.find(pattern) != std::string::npos; } -#define ASSERT_TEST_FAILS(TEST_EXPR) \ - ASSERT_THROWS((TEST_EXPR), mongo::unittest::TestAssertionFailureException) +#define ASSERT_TEST_FAILS(TEST_STMT) \ + ASSERT_THROWS(TEST_STMT, mongo::unittest::TestAssertionFailureException) -#define ASSERT_TEST_FAILS_MATCH(TEST_EXPR, PATTERN) \ +#define ASSERT_TEST_FAILS_MATCH(TEST_STMT, PATTERN) \ ASSERT_THROWS_PRED( \ - TEST_EXPR, \ + TEST_STMT, \ mongo::unittest::TestAssertionFailureException, \ stdx::bind(containsPattern, \ PATTERN, \ @@ -122,6 +122,12 @@ namespace { ASSERT_TEST_FAILS_MATCH(ASSERT_TRUE(false) << "Told you so", "Told you so"); ASSERT_TEST_FAILS_MATCH(ASSERT(false) << "Told you so", "Told you so"); ASSERT_TEST_FAILS_MATCH(ASSERT_FALSE(true) << "Told you so", "Told you so"); + ASSERT_TEST_FAILS_MATCH(ASSERT_EQUALS(1, 2) << "Told you so", "Told you so"); + } + + TEST(UnitTestSelfTest, TestNoDoubleEvaluation) { + int i = 0; + ASSERT_TEST_FAILS_MATCH(ASSERT_EQ(0, ++i), "(0 == 1)"); } } // namespace |