From 6e829c4fb865d9fb80d41a61482d6fc910652536 Mon Sep 17 00:00:00 2001 From: Andreas Waidler Date: Sat, 27 Feb 2010 00:14:35 +0100 Subject: [PATCH] Added Exception and its tests. --- include/libsex/Exception.hxx | 61 +++++++++++++++++++++ include/libsex/Makefile.am | 2 +- src/Exception.cxx | 85 +++++++++++++++++++++++++++++ src/Makefile.am | 6 +-- tests/CountingException.cxx | 21 ++++++++ tests/CountingException.hxx | 18 +++++++ tests/Makefile.am | 5 +- tests/TestException.cxx | 118 +++++++++++++++++++++++++++++++++++++++++ tests/TestException.hxx | 32 +++++++++++ tests/UncloneableException.cxx | 12 +++++ tests/UncloneableException.hxx | 16 ++++++ tests/testRegistry.cxx | 3 +- 12 files changed, 373 insertions(+), 6 deletions(-) create mode 100644 include/libsex/Exception.hxx create mode 100644 src/Exception.cxx create mode 100644 tests/CountingException.cxx create mode 100644 tests/CountingException.hxx create mode 100644 tests/TestException.cxx create mode 100644 tests/TestException.hxx create mode 100644 tests/UncloneableException.cxx create mode 100644 tests/UncloneableException.hxx diff --git a/include/libsex/Exception.hxx b/include/libsex/Exception.hxx new file mode 100644 index 0000000..7d4a06c --- /dev/null +++ b/include/libsex/Exception.hxx @@ -0,0 +1,61 @@ +#ifndef EXCEPTION_HXX +#define EXCEPTION_HXX + +#include // std::exception +#include // std::bad_alloc +#include // std::ostream + +namespace libsex { + +/** + * @brief Resembles a generic error, stores a + * message and optionally references one + * previous error (linked list). + * + * For OO error handling you should subclass this + * class for every type of error. Inheriting by + * hand is tedious, you usally don't want to do + * this. There are some efficient macros in @file + * create.hxx. + * + * @note The previous exceptions, if any, will + * be copied by calling clone() which may + * in turn throw bad_alloc. In that case, a + * notice is appended to this message (if + * enough chars left) and the exception is + * swallowed. + */ +class Exception : public std::exception +{ +public: + static const unsigned short LENGTH = 500; + + Exception(const char* const message) throw(); + Exception( + const char* const message, + const Exception& previous) throw(); + virtual ~Exception() throw(); + + void write(std::ostream& out) const; + void backtrace(std::ostream& out) const; + + virtual const Exception* clone() const + throw(std::bad_alloc) ; + + virtual const char* what() const throw(); + +private: + /** + * @brief Copy of message passed to constructor. + * + * Length is Exception::LENGTH plus nullbyte. + */ + char _message[LENGTH + 1]; + const Exception* _previous; + + void _initMessage(const char* const message); +}; + +}; // namespace + +#endif diff --git a/include/libsex/Makefile.am b/include/libsex/Makefile.am index a099e95..b84e390 100644 --- a/include/libsex/Makefile.am +++ b/include/libsex/Makefile.am @@ -1,7 +1,7 @@ SUBDIRS = installdir = $(includedir)/$(PACKAGE) -install_HEADERS = +install_HEADERS = Exception.hxx uninstall-hook: rm -rf $(includedir)/$(PACKAGE) diff --git a/src/Exception.cxx b/src/Exception.cxx new file mode 100644 index 0000000..8a46d08 --- /dev/null +++ b/src/Exception.cxx @@ -0,0 +1,85 @@ +#include +#include // strncpy() + +namespace libsex { + +Exception::Exception(const char* const message) throw() +: _previous(0) +{ + _initMessage(message); +} + +Exception::Exception( + const char* const message, + const Exception& previous) throw() +: _previous(0) +{ + _initMessage(message); + try { + _previous = previous.clone(); + } catch (std::bad_alloc& e) { + // We need to know the amount of characters + // that can be put into _message. + + // Size to begin with: + unsigned int length = Exception::LENGTH; + // Substract characters written so far: + length -= strlen(_message); + // Substract nullbyte appended by strcat(). + // Prevent underflow. + if (length > 0) --length; + + // Add notice to our original message: + strncat(_message, "\n" + "DOUBLE FAULT! " + "Caught std::bad_alloc in " + "constructor of an exception.", + length); + } +} + +Exception::~Exception() throw() +{ + if (_previous) delete _previous; +} + +void Exception::write(std::ostream& out) const +{ + out << what(); +} + +void Exception::backtrace(std::ostream& out) const +{ + write(out); + if (_previous != 0) { + out << "\n"; + _previous->backtrace(out); + } +} + +const Exception* Exception::clone() const +throw(std::bad_alloc) +{ + if (_previous) { + return new Exception(_message, *_previous); + } else { + return new Exception(_message); + } +} + +const char* Exception::what() const throw() +{ + return _message; +} + +void Exception::_initMessage(const char* const message) +{ + strncpy(_message, message, Exception::LENGTH); + // if |message| < |_message| + // strncpy() will pad with 0 + // else + // strncpy() will NOT add a trailing 0! + _message[Exception::LENGTH] = 0; +} + +} // namespace diff --git a/src/Makefile.am b/src/Makefile.am index f1a19ee..1a3755c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,7 @@ -SUBDIRS = +SUBDIRS = lib_LTLIBRARIES = libsex.la libsex_la_CPPFLAGS = -I$(top_srcdir)/include libsex_la_CXXFLAGS = -libsex_la_LIBADD = -libsex_la_SOURCES = +libsex_la_LIBADD = +libsex_la_SOURCES = Exception.cxx diff --git a/tests/CountingException.cxx b/tests/CountingException.cxx new file mode 100644 index 0000000..a3aae59 --- /dev/null +++ b/tests/CountingException.cxx @@ -0,0 +1,21 @@ +#include + +int CountingException::instances = 0; + +CountingException::CountingException( + const char* const message) +: libsex::Exception(message) +{ + CountingException::instances++; +} + +CountingException::~CountingException() throw() +{ + CountingException::instances--; +} + +const libsex::Exception* +CountingException::clone() const throw(std::bad_alloc) +{ + return new CountingException("CountingException"); +} diff --git a/tests/CountingException.hxx b/tests/CountingException.hxx new file mode 100644 index 0000000..0431894 --- /dev/null +++ b/tests/CountingException.hxx @@ -0,0 +1,18 @@ +#ifndef COUNTINGEXCEPTION_HXX +#define COUNTINGEXCEPTION_HXX + +#include + +class CountingException : public libsex::Exception +{ +public: + static int instances; + + CountingException(const char* const); + ~CountingException() throw(); + + const libsex::Exception* + clone() const throw(std::bad_alloc); +}; + +#endif diff --git a/tests/Makefile.am b/tests/Makefile.am index 6ad6524..436c370 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -8,7 +8,10 @@ TESTS = tests tests_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir) $(CPPUNIT_CFLAGS) tests_CXXFLAGS = $(CPPUNIT_LIBS) tests_LDADD = $(top_srcdir)/src/$(PACKAGE).la -tests_SOURCES = main.cxx testRegistry.cxx +tests_SOURCES = main.cxx testRegistry.cxx \ + CountingException.cxx \ + UncloneableException.cxx \ + TestException.cxx else diff --git a/tests/TestException.cxx b/tests/TestException.cxx new file mode 100644 index 0000000..0643f6b --- /dev/null +++ b/tests/TestException.cxx @@ -0,0 +1,118 @@ +#include +#include +#include + +#include +#include + +void TestException::testCopyingOfShortMessage() +{ + char* const cstr = new char[80]; + strcpy(cstr, "TEST0001"); + libsex::Exception e(cstr); + delete cstr; + + std::string exp("TEST0001"); + std::string act(e.what()); + CPPUNIT_ASSERT_EQUAL(exp, act); +} + +void TestException::testWhetherLongMessageDoesNotOverflow() +{ + unsigned int length = libsex::Exception::LENGTH; + std::string str; + + for (int i = 0; i < length; ++i) { + str += '.'; + } + str += '!'; + + libsex::Exception e(str.c_str()); + + CPPUNIT_ASSERT_EQUAL(length, strlen(e.what())); + + for (int i = 0; i < length; ++i) { + CPPUNIT_ASSERT_EQUAL('.', e.what()[i]); + } +} + +void TestException::testWhetherPreviousExceptionIsCloned() +{ + CPPUNIT_ASSERT_EQUAL(0, CountingException::instances); + CountingException e1("e1"); + CPPUNIT_ASSERT_EQUAL(1, CountingException::instances); + libsex::Exception e2("e2", e1); + CPPUNIT_ASSERT_EQUAL(2, CountingException::instances); +} + +void TestException::testWhetherClonedExceptionIsDeleted() +{ + CPPUNIT_ASSERT_EQUAL(0, CountingException::instances); + + CountingException e1("e1"); + CPPUNIT_ASSERT_EQUAL(1, CountingException::instances); + { + libsex::Exception e2("e2", e1); + CPPUNIT_ASSERT_EQUAL(2, CountingException::instances); + } + CPPUNIT_ASSERT_EQUAL(1, CountingException::instances); +} + +void TestException::testWhetherBadAllocOnCloneIsSwallowed() +{ + UncloneableException e1; + libsex::Exception e2("e2", e1); +} + +void TestException::testWhetherNoticeIsAppendedOnBadAlloc() +{ + UncloneableException e1; + libsex::Exception e2("e2", e1); + + std::string exp + = "e2\nDOUBLE FAULT! " + "Caught std::bad_alloc in constructor " + "of an exception."; + std::string act = e2.what(); + CPPUNIT_ASSERT_EQUAL(exp, act); +} + +void TestException::testWhetherNoticeDoesNotOverflowIfBufferTooSmall() +{ + unsigned int length = libsex::Exception::LENGTH; + std::string str; + + for (int i = 0; i < length; ++i) { + str += '.'; + } + + UncloneableException e1; + libsex::Exception e2(str.c_str(), e1); + + CPPUNIT_ASSERT_EQUAL(length, strlen(e2.what())); + CPPUNIT_ASSERT_EQUAL(str, std::string(e2.what())); +} + +void TestException::testWritingToStream() +{ + std::string exp("TestException"); + libsex::Exception e(exp.c_str()); + + std::stringstream act; + e.write(act); + + CPPUNIT_ASSERT_EQUAL(exp, act.str()); +} + +void TestException::testBacktrace() +{ + libsex::Exception cause("Failed to foo."); + libsex::Exception e("Failed to bar.", cause); + + std::stringstream act; + e.backtrace(act); + + CPPUNIT_ASSERT_EQUAL( + std::string("Failed to bar.\nFailed to foo."), + act.str()); +} diff --git a/tests/TestException.hxx b/tests/TestException.hxx new file mode 100644 index 0000000..989d177 --- /dev/null +++ b/tests/TestException.hxx @@ -0,0 +1,32 @@ +#ifndef TESTEXCEPTION_HXX +#define TESTEXCEPTION_HXX + +#include + +class TestException : public CppUnit::TestFixture +{ +CPPUNIT_TEST_SUITE(TestException); + CPPUNIT_TEST(testCopyingOfShortMessage); + CPPUNIT_TEST(testWhetherLongMessageDoesNotOverflow); + CPPUNIT_TEST(testWhetherPreviousExceptionIsCloned); + CPPUNIT_TEST(testWhetherClonedExceptionIsDeleted); + CPPUNIT_TEST(testWhetherBadAllocOnCloneIsSwallowed); + CPPUNIT_TEST(testWhetherNoticeIsAppendedOnBadAlloc); + CPPUNIT_TEST(testWhetherNoticeDoesNotOverflowIfBufferTooSmall); + CPPUNIT_TEST(testWritingToStream); + CPPUNIT_TEST(testBacktrace); +CPPUNIT_TEST_SUITE_END(); + +public: + void testCopyingOfShortMessage(); + void testWhetherLongMessageDoesNotOverflow(); + void testWhetherPreviousExceptionIsCloned(); + void testWhetherClonedExceptionIsDeleted(); + void testWhetherBadAllocOnCloneIsSwallowed(); + void testWhetherNoticeIsAppendedOnBadAlloc(); + void testWhetherNoticeDoesNotOverflowIfBufferTooSmall(); + void testWritingToStream(); + void testBacktrace(); +}; + +#endif diff --git a/tests/UncloneableException.cxx b/tests/UncloneableException.cxx new file mode 100644 index 0000000..9eb094b --- /dev/null +++ b/tests/UncloneableException.cxx @@ -0,0 +1,12 @@ +#include + +UncloneableException::UncloneableException() +: libsex::Exception("UncloneableException") { } + +UncloneableException::~UncloneableException() throw() { } + +const libsex::Exception* +UncloneableException::clone() const throw(std::bad_alloc) +{ + throw std::bad_alloc(); +} diff --git a/tests/UncloneableException.hxx b/tests/UncloneableException.hxx new file mode 100644 index 0000000..375de12 --- /dev/null +++ b/tests/UncloneableException.hxx @@ -0,0 +1,16 @@ +#ifndef UNCLONEABLEEXCEPTION_HXX +#define UNCLONEABLEEXCEPTION_HXX + +#include + +class UncloneableException : public libsex::Exception +{ +public: + UncloneableException(); + ~UncloneableException() throw(); + + const libsex::Exception* + clone() const throw(std::bad_alloc); +}; + +#endif diff --git a/tests/testRegistry.cxx b/tests/testRegistry.cxx index b3323db..62a8c6e 100644 --- a/tests/testRegistry.cxx +++ b/tests/testRegistry.cxx @@ -3,4 +3,5 @@ // Registering all unit tests here, to make them // easier to disable and enable. -//CPPUNIT_TEST_SUITE_REGISTRATION(); +#include +CPPUNIT_TEST_SUITE_REGISTRATION(TestException); -- 2.11.4.GIT