/* * ObserverTest.cpp * * Created on: Jan 19, 2010 * Author: crueger */ #include "ObserverTest.hpp" #include #include #include #include #include "Patterns/Observer.hpp" #include "Patterns/ObservedIterator.hpp" #include "Helpers/Assert.hpp" #include using namespace std; #ifdef HAVE_TESTRUNNER #include "UnitTestMain.hpp" #endif /*HAVE_TESTRUNNER*/ // Registers the fixture into the 'registry' CPPUNIT_TEST_SUITE_REGISTRATION( ObserverTest ); /******************* Test stubs ************************/ class UpdateCountObserver : public Observer { public: UpdateCountObserver() : Observer("UpdateCountObserver"), updates(0) {}; void update(Observable *publisher){ updates++; } void subjectKilled(Observable *publisher) { } int updates; }; class SimpleObservable : public Observable { public: SimpleObservable() : Observable("SimpleObservable") {} void changeMethod() { OBSERVE; int i = 0; i++; } }; class CallObservable : public Observable { public: CallObservable() : Observable("CallObservable") {} void changeMethod1() { OBSERVE; int i = 0; i++; } void changeMethod2() { OBSERVE; int i = 0; i++; changeMethod1(); } }; class BlockObservable : public Observable { public: BlockObservable() : Observable("BlockObservable") {} void changeMethod1(){ OBSERVE; // test if we report correctly as blocked CPPUNIT_ASSERT(isBlocked()); } void changeMethod2(){ OBSERVE; internalMethod1(); internalMethod2(); } void internalMethod1(){ // we did not block, but our caller did... // see if this is found CPPUNIT_ASSERT(isBlocked()); } void internalMethod2(){ OBSERVE; // Both this method and the caller do block // Does the reporting still work as expected? CPPUNIT_ASSERT(isBlocked()); } void noChangeMethod(){ // No Block introduced here // reported correctely? CPPUNIT_ASSERT(!isBlocked()); } }; class SuperObservable : public Observable { public: SuperObservable(): Observable("SuperObservable") { subObservable = new SimpleObservable(); subObservable->signOn(this); } ~SuperObservable(){ delete subObservable; } void changeMethod() { OBSERVE; int i = 0; i++; subObservable->changeMethod(); } SimpleObservable *subObservable; }; class NotificationObservable : public Observable { public: NotificationObservable() : Observable("NotificationObservable"), notification1(new Notification(this)), notification2(new Notification(this)) {} ~NotificationObservable(){ delete notification1; delete notification2; } void operation1(){ OBSERVE; NOTIFY(notification1); } void operation2(){ OBSERVE; NOTIFY(notification2); } Notification_ptr notification1; Notification_ptr notification2; }; class NotificationObserver : public Observer { public: NotificationObserver(Notification_ptr notification) : Observer("NotificationObserver"), requestedNotification(notification), wasNotified(false) {} void update(Observable*){} void subjectKilled(Observable*){} void recieveNotification(Observable *publisher, Notification_ptr notification){ ASSERT(requestedNotification==notification,"Notification received that was not requested"); wasNotified = true; } Notification_ptr requestedNotification; bool wasNotified; }; class ObservableSet : public Observable { public: typedef std::set set; typedef ObservedIterator iterator; typedef set::const_iterator const_iterator; ObservableSet(int _num) : Observable("ObservableCollection"), num(_num) { for(int i=0; isignOn(this); theSet.insert(content); } } ~ObservableSet(){ set::iterator iter; for(iter=theSet.begin(); iter!=theSet.end(); ++iter ){ delete (*iter); } } iterator begin(){ return iterator(theSet.begin(),this); } iterator end(){ return iterator(theSet.end(),this); } const int num; private: set theSet; }; class ObservableMap : public Observable { public: typedef std::map set; typedef ObservedIterator iterator; typedef set::const_iterator const_iterator; ObservableMap(int _num) : Observable("ObservableCollection"), num(_num) { for(int i=0; isignOn(this); theSet.insert(make_pair(i,content)); } } ~ObservableMap(){ set::iterator iter; for(iter=theSet.begin(); iter!=theSet.end(); ++iter ){ delete iter->second; } } iterator begin(){ return iterator(theSet.begin(),this); } iterator end(){ return iterator(theSet.end(),this); } const int num; private: set theSet; }; /******************* actuall tests ***************/ void ObserverTest::setUp() { ASSERT_DO(Assert::Throw); simpleObservable1 = new SimpleObservable(); simpleObservable2 = new SimpleObservable(); callObservable = new CallObservable(); superObservable = new SuperObservable(); blockObservable = new BlockObservable(); notificationObservable = new NotificationObservable(); obsset = new ObservableSet(5); obsmap = new ObservableMap(5); observer1 = new UpdateCountObserver(); observer2 = new UpdateCountObserver(); observer3 = new UpdateCountObserver(); observer4 = new UpdateCountObserver(); notificationObserver1 = new NotificationObserver(notificationObservable->notification1); notificationObserver2 = new NotificationObserver(notificationObservable->notification2); } void ObserverTest::tearDown() { delete simpleObservable1; delete simpleObservable2; delete callObservable; delete superObservable; delete blockObservable; delete notificationObservable; delete obsset; delete obsmap; delete observer1; delete observer2; delete observer3; delete observer4; delete notificationObserver1; delete notificationObserver2; } void ObserverTest::doesUpdateTest() { simpleObservable1->signOn(observer1); simpleObservable1->signOn(observer2); simpleObservable1->signOn(observer3); simpleObservable2->signOn(observer2); simpleObservable2->signOn(observer4); CPPUNIT_ASSERT_EQUAL( 0, observer1->updates ); CPPUNIT_ASSERT_EQUAL( 0, observer2->updates ); CPPUNIT_ASSERT_EQUAL( 0, observer3->updates ); CPPUNIT_ASSERT_EQUAL( 0, observer4->updates ); simpleObservable1->changeMethod(); CPPUNIT_ASSERT_EQUAL( 1, observer1->updates ); CPPUNIT_ASSERT_EQUAL( 1, observer2->updates ); CPPUNIT_ASSERT_EQUAL( 1, observer3->updates ); CPPUNIT_ASSERT_EQUAL( 0, observer4->updates ); simpleObservable1->signOff(observer3); simpleObservable1->changeMethod(); CPPUNIT_ASSERT_EQUAL( 2, observer1->updates ); CPPUNIT_ASSERT_EQUAL( 2, observer2->updates ); CPPUNIT_ASSERT_EQUAL( 1, observer3->updates ); CPPUNIT_ASSERT_EQUAL( 0, observer4->updates ); simpleObservable2->changeMethod(); CPPUNIT_ASSERT_EQUAL( 2, observer1->updates ); CPPUNIT_ASSERT_EQUAL( 3, observer2->updates ); CPPUNIT_ASSERT_EQUAL( 1, observer3->updates ); CPPUNIT_ASSERT_EQUAL( 1, observer4->updates ); } void ObserverTest::doesBlockUpdateTest() { callObservable->signOn(observer1); CPPUNIT_ASSERT_EQUAL( 0, observer1->updates ); callObservable->changeMethod1(); CPPUNIT_ASSERT_EQUAL( 1, observer1->updates ); callObservable->changeMethod2(); CPPUNIT_ASSERT_EQUAL( 2, observer1->updates ); } void ObserverTest::doesSubObservableTest() { superObservable->signOn(observer1); superObservable->subObservable->signOn(observer2); superObservable->subObservable->changeMethod(); CPPUNIT_ASSERT_EQUAL( 1, observer1->updates ); CPPUNIT_ASSERT_EQUAL( 1, observer2->updates ); superObservable->changeMethod(); CPPUNIT_ASSERT_EQUAL( 2, observer1->updates ); CPPUNIT_ASSERT_EQUAL( 2, observer2->updates ); } void ObserverTest::outsideLockTest(){ callObservable->signOn(observer1); CPPUNIT_ASSERT_EQUAL( 0, observer1->updates ); { LOCK_OBSERVABLE(*callObservable); CPPUNIT_ASSERT_EQUAL( 0, observer1->updates ); } // lock is gone now, observer should have notified CPPUNIT_ASSERT_EQUAL( 1, observer1->updates ); } void ObserverTest::doesNotifyTest(){ notificationObservable->signOn(notificationObserver1, notificationObservable->notification1); notificationObservable->signOn(notificationObserver2, notificationObservable->notification2); notificationObservable->operation1(); CPPUNIT_ASSERT(notificationObserver1->wasNotified); CPPUNIT_ASSERT(!notificationObserver2->wasNotified); notificationObserver1->wasNotified=false; notificationObservable->operation2(); CPPUNIT_ASSERT(!notificationObserver1->wasNotified); CPPUNIT_ASSERT(notificationObserver2->wasNotified); } void ObserverTest::doesReportTest(){ // Actual checks are in the Stub-methods for this blockObservable->changeMethod1(); blockObservable->changeMethod2(); blockObservable->noChangeMethod(); } void ObserverTest::iteratorTest(){ int i = 0; // test the general iterator methods for(ObservableSet::iterator iter=obsset->begin(); iter!=obsset->end();++iter){ CPPUNIT_ASSERT(i< obsset->num); i++; } i=0; for(ObservableSet::const_iterator iter=obsset->begin(); iter!=obsset->end();++iter){ CPPUNIT_ASSERT(inum); i++; } obsset->signOn(observer1); { // we construct this out of the loop, so the iterator dies at the end of // the scope and not the end of the loop (allows more testing) ObservableSet::iterator iter; for(iter=obsset->begin(); iter!=obsset->end(); ++iter){ (*iter)->changeMethod(); } // At this point no change should have been propagated CPPUNIT_ASSERT_EQUAL( 0, observer1->updates); } // After the Iterator has died the propagation should take place CPPUNIT_ASSERT_EQUAL( 1, observer1->updates); // when using a const_iterator no changes should be propagated for(ObservableSet::const_iterator iter = obsset->begin(); iter!=obsset->end();++iter); CPPUNIT_ASSERT_EQUAL( 1, observer1->updates); // we need to test the operator-> as well obsmap->signOn(observer2); { // we construct this out of the loop, so the iterator dies at the end of // the scope and not the end of the loop (allows more testing) ObservableMap::iterator iter; for(iter=obsmap->begin(); iter!=obsmap->end(); ++iter){ iter->second->changeMethod(); } // At this point no change should have been propagated CPPUNIT_ASSERT_EQUAL( 0, observer2->updates); } // After the Iterator has died the propagation should take place CPPUNIT_ASSERT_EQUAL( 1, observer2->updates); obsset->signOff(observer1); obsmap->signOff(observer2); } void ObserverTest::CircleDetectionTest() { cout << endl << "Warning: the next test involved methods that can produce infinite loops." << endl; cout << "Errors in this methods can not be checked using the CPPUNIT_ASSERT Macros." << endl; cout << "Instead tests are run on these methods to see if termination is assured" << endl << endl; cout << "If this test does not complete in a few seconds, kill the test-suite and fix the Error in the circle detection mechanism" << endl; cout << endl << endl << "The following errors displayed by the observer framework can be ignored" << endl; // make this Observable its own subject. NEVER DO THIS IN ACTUAL CODE simpleObservable1->signOn(simpleObservable1); #ifndef NDEBUG CPPUNIT_ASSERT_THROW(simpleObservable1->changeMethod(),Assert::AssertionFailure); #else simpleObservable1->changeMethod(); #endif // more complex test simpleObservable1->signOff(simpleObservable1); simpleObservable1->signOn(simpleObservable2); simpleObservable2->signOn(simpleObservable1); #ifndef NDEBUG CPPUNIT_ASSERT_THROW(simpleObservable1->changeMethod(),Assert::AssertionFailure); #else simpleObservable1->changeMethod(); #endif simpleObservable1->signOff(simpleObservable2); simpleObservable2->signOff(simpleObservable1); // when we reach this line, although we broke the DAG assumption the circle check works fine CPPUNIT_ASSERT(true); }