#include "gtest/gtest.h" #include "ariba/utility/system/SystemQueue.h" #include // usleep #include // local_time() #include using namespace ::testing; using namespace ariba::utility; using namespace std; using boost::posix_time::microsec_clock; /** * Tests if the SystemQueue is initialized empty and not running. */ TEST(SystemQueue, Instantiation) { SystemQueue& sysq = SystemQueue::instance(); // cout << &sysq << endl; EXPECT_FALSE( sysq.isRunning() ); EXPECT_TRUE( sysq.isEmpty() ); } /** * Tests whether calling the SystemQueue::instance() always returns the same object. * * NOTE: This is an easy case, since this is the same compile unit.. * But can't hurt to test it anyway. */ TEST(SystemQueue, Singleton) { SystemQueue& sysq = SystemQueue::instance(); SystemQueue& sysq2 = SystemQueue::instance(); // cout << &sysq << endl; EXPECT_TRUE( &sysq == &sysq2 ); } /** * Start and stop the SystemQueue */ TEST(SystemQueue, StartStop) { SystemQueue& sysq = SystemQueue::instance(); EXPECT_FALSE( sysq.isRunning() ); // start sysq.run(); EXPECT_TRUE( sysq.isRunning() ); // stop sysq.cancel(); // XXX weg, wenn das join klappt!! usleep(100); EXPECT_FALSE( sysq.isRunning() ); } /** * Test fixture * * class that can be called by the SystemQueue */ // To use a test fixture, derive a class from testing::Test. class SystemQueueTest : public testing::Test { public: // sleep time when waiting for the system queue (max total / each step) #define MAX_WAIT 100 // microseconds #define SLEEP_TIME 10 // microseconds SystemQueueTest() : checkmark(false), last_ordered_call(0) { } void Check() { cout << "### Check ### "<< endl; checkmark = true; } void CheckThread() { EXPECT_TRUE( SystemQueue::instance().am_I_in_the_SysQ_thread() ); checkmark = true; } void Cancel() { ASSERT_THROW(SystemQueue::instance().cancel(), std::logic_error); checkmark = true; } void Leave() { // let's do something before leaving.. this->LongRunner(); checkmark = false; cout << "### Leaving SysQ ### "<< endl; SystemQueue::instance().leave(); } void LongRunner() { usleep( MAX_WAIT / 2 ); checkmark = true; } /// wait for the checkmark to be set by a SystemQueue call, (but not too long!) void wait_for_checkmark(int max_wait = MAX_WAIT) { for ( int i = 0; i < max_wait / SLEEP_TIME; i++) { if ( checkmark ) break; cout << "### sleeping for " << SLEEP_TIME << " microseconds ..." << endl; usleep(SLEEP_TIME); } } /// call that checks wheather it was performed in order void OrderedCall(int num) { // XXX cout << "### OrderedCall num: " << num << endl; // check ordering EXPECT_EQ( num, last_ordered_call + 1); last_ordered_call = num; } /// like OrderedCall, but calls itself to test nested calls void NestedOrderedCall(int from, int to) { // check ordering OrderedCall(from); // nested call if ( from < to ) { SystemQueue::instance().scheduleCall( boost::bind(&SystemQueueTest::NestedOrderedCall, this, from+1, to) ); } else { /// XXX because the current/old SystemQueue does not pass the Threading test, /// we have to signal, that when all nested calls are finished, /// so we need to set the checkmark here.. checkmark = true; } } bool checkmark; int last_ordered_call; }; /** * Enqueues an event but then cancels the SystemQueue without running */ TEST_F(SystemQueueTest, EmptyAfterCancel) { SystemQueue& sysq = SystemQueue::instance(); EXPECT_TRUE( sysq.isEmpty() ); // enqueue event sysq.scheduleCall( boost::bind(&SystemQueueTest::Check, this) ); EXPECT_FALSE( sysq.isEmpty() ); // cancel sysq.cancel(); EXPECT_TRUE( sysq.isEmpty() ); } /** * cancels the SystemQueue from inside a scheduled event * * --> and expects that this throws an exception * * NOTE: due to the SystemQueue singleton design, explicit cleanup is necessary! */ TEST_F(SystemQueueTest, CancelFromInsideEvent) { SystemQueue& sysq = SystemQueue::instance(); checkmark = false; // just to be sure.. // start sysq.run(); // scheduleCall sysq.scheduleCall( boost::bind(&SystemQueueTest::Cancel, this) ); // wait for the event to happen wait_for_checkmark(MAX_WAIT); // CLEANUP sysq.cancel(); EXPECT_FALSE( sysq.isRunning() ); EXPECT_TRUE( sysq.isEmpty() ); } /** * leaves the SystemQueue from inside a scheduled event (in contrast to cancel) * * NOTE: due to the SystemQueue singleton design, explicit cleanup is necessary! * * [ NOTE: the old System Queue does not support this. ] */ TEST_F(SystemQueueTest, LeaveFromInsideEvent) { SystemQueue& sysq = SystemQueue::instance(); // start sysq.run(); // scheduleCall sysq.scheduleCall( boost::bind(&SystemQueueTest::Leave, this) ); // schedule another which must NOT be performed sysq.scheduleCall( boost::bind(&SystemQueueTest::Check, this) ); // sleep until the SystemQueue thread has finished sysq.join(); EXPECT_FALSE( sysq.isRunning() ); // ensure Check() was not perfomed EXPECT_FALSE( sysq.isEmpty() ); EXPECT_FALSE( checkmark ); // CLEANUP sysq.cancel(); EXPECT_FALSE( sysq.isRunning() ); EXPECT_TRUE( sysq.isEmpty() ); } /** * schedule a call and test whether it is actually performed by the SystemQueue */ TEST_F(SystemQueueTest, ScheduleCall) { SystemQueue& sysq = SystemQueue::instance(); checkmark = false; // just to be sure.. // start sysq.run(); // scheduleCall sysq.scheduleCall( boost::bind(&SystemQueueTest::Check, this) ); // wait for the event to happen wait_for_checkmark(MAX_WAIT); // stop sysq.cancel(); EXPECT_TRUE( checkmark ) << "Function was not called within " << MAX_WAIT << " microseconds."; } /** * This test actually tests two things [sorry, but it's hard to test them separately!] * * - first: the SystemQueue should not consider itself empty, while an event is processed * - second: SystemQueue events should be processed in parallel to the main thread * * NOTE: The timings here are not obvious, maybe they have to be adjusted on very slow machines * * NOTE: !! The current/old SystemQueue does NOT pass this test!! * * That's also why we need the unhandy wait_for_checkmark function, * instead a wait_until_empty function. */ TEST_F(SystemQueueTest, Threading) { SystemQueue& sysq = SystemQueue::instance(); checkmark = false; // just to be sure.. // start sysq.run(); // scheduleCall sysq.scheduleCall( boost::bind(&SystemQueueTest::LongRunner, this) ); // SystemQueue must not be empty as long as the event is not finished if ( sysq.isEmpty() ) { // assert that this test is actually meaningful ASSERT_FALSE( checkmark ) << "NOTE: This is not necessarily a bug, maybe the timing just have to adjusted. Try to increase MAX_WAIT."; EXPECT_TRUE( ! sysq.isEmpty() || checkmark ); } // wait for the event to finish wait_for_checkmark(MAX_WAIT); // stop sysq.cancel(); // even the long-runner should finally finish EXPECT_TRUE( checkmark ) << "Function has not finished within " << MAX_WAIT << " microseconds."; } TEST_F(SystemQueueTest, ThreadDetection) { SystemQueue& sysq = SystemQueue::instance(); // start sysq.run(); EXPECT_FALSE( sysq.am_I_in_the_SysQ_thread() ); // scheduleCall sysq.scheduleCall( boost::bind(&SystemQueueTest::CheckThread, this) ); wait_for_checkmark(MAX_WAIT); sysq.cancel(); EXPECT_TRUE( checkmark ) << "WARNING! One of the test functions was not called at all!"; } /** * schedule multiple calls * * each call must be performed, in the correct order * * NOTE: The nested calls are not necessarily in order with calls scheduled from the main thread, * that's fine, therefore we make sure the nested calls are done, before scheduling new ones. */ TEST_F(SystemQueueTest, MultipleCalls) { SystemQueue& sysq = SystemQueue::instance(); // start sysq.run(); sysq.scheduleCall( boost::bind(&SystemQueueTest::OrderedCall, this, 1) ); sysq.scheduleCall( boost::bind(&SystemQueueTest::OrderedCall, this, 2) ); sysq.scheduleCall( boost::bind(&SystemQueueTest::NestedOrderedCall, this, 3, 5) ); // make sure all nested calls are done wait_for_checkmark(MAX_WAIT); // XXX should be "wait_until_empty() ...." checkmark = false; sysq.scheduleCall( boost::bind(&SystemQueueTest::OrderedCall, this, 6) ); // XXX same here... [ wait_until_empty() ] sysq.scheduleCall( boost::bind(&SystemQueueTest::Check, this) ); wait_for_checkmark(MAX_WAIT); // evaluation EXPECT_EQ( 6, last_ordered_call); // stop sysq.cancel(); } /** * This subclass has some special member functions suitable for timing tests * * NOTE: Timing tests are not always reproducable.. sorry. :-/ */ class SystemQueueTimingTest : public SystemQueueTest { public: /** * typical delay time * * should be long enough for meaningful tests, * but should not lengthen the test excessivly */ #define DELAY_TIME 20 // ms #define DELAY_MARGIN 1000 // microseconds (1/1000 ms) // NOTE: maybe a meaningful value depends // on the target hardwarde and system load... /// constructor SystemQueueTimingTest() : SystemQueueTest() /* super constructor */, sysq( SystemQueue::instance() ), calls(0) { } virtual void SetUp() { // start SystemQueue sysq.run(); } virtual void TearDown() { // stop SystemQueue sysq.cancel(); } /** * @param placed_in_queue The time (as ptime) when this event is put into the delay queue * @param intended_sleep_time The time (in microseconds) the event is supposed to sleep in the queue * @param margin The acceptable margin (in microseconds) */ void TimeCheckingCall(ptime placed_in_queue, uint64_t intended_sleep_time, uint64_t margin) { ptime called_at = microsec_clock::local_time(); // calculate actual sleep time and difference to intended sleep time boost::posix_time::time_duration actual_sleep_time = called_at - placed_in_queue; uint64_t diff = actual_sleep_time.total_microseconds() - intended_sleep_time; // info cout << "### Timing difference: " << diff << " microseconds" << endl; EXPECT_LT( abs(diff), margin ); calls++; checkmark = true; } SystemQueue& sysq; int calls; }; /** * schedules a delayed call and tests whether it is called (more or less timely..) */ TEST_F(SystemQueueTimingTest, DelayedCall) { // scheduleCall sysq.scheduleCall( boost::bind(&SystemQueueTimingTest::Check, this), DELAY_TIME ); // noting to do until the delay is up.. usleep(DELAY_TIME*1000 + DELAY_MARGIN*10); // wait for the event to happen wait_for_checkmark(MAX_WAIT); EXPECT_TRUE( checkmark ) << "Delayed function was not called within delaytime (" << DELAY_TIME << " ms) + " << (MAX_WAIT + DELAY_MARGIN) << " microseconds."; } /** * tests whether the SystemQueue is considered non-empty, while an event is delayed-waiting */ TEST_F(SystemQueueTimingTest, NotEmptyWhileWaiting) { // scheduleCall sysq.scheduleCall( boost::bind(&SystemQueueTimingTest::Check, this), DELAY_TIME ); // SystemQueue must not be empty as long as the event is not finished (and especially while stille queued) if ( sysq.isEmpty() ) { // assert that this test is actually meaningful ASSERT_FALSE( checkmark ) << "NOTE: This is not necessarily a bug, maybe the timing just have to adjusted. Try to increase MAX_WAIT."; EXPECT_TRUE( ! sysq.isEmpty() || checkmark ); } } /** * schedules a delayed call and tests whether it is called (more or less timely..) */ TEST_F(SystemQueueTimingTest, MultipleCalls) { // schedule 4th call sysq.scheduleCall( boost::bind(&SystemQueueTest::OrderedCall, this, 4), DELAY_TIME*3 ); // schedule 2nd call sysq.scheduleCall( boost::bind(&SystemQueueTest::OrderedCall, this, 2), DELAY_TIME*1 ); // schedule 3rd call sysq.scheduleCall( boost::bind(&SystemQueueTest::OrderedCall, this, 3), DELAY_TIME*2 ); // schedule 1st call (without delay) sysq.scheduleCall( boost::bind(&SystemQueueTest::OrderedCall, this, 1) ); // XXX the usual bug.. sysq.scheduleCall( boost::bind(&SystemQueueTimingTest::Check, this), DELAY_TIME*4 ); // noting to do until the delay is up.. usleep(DELAY_TIME * 4000 + DELAY_MARGIN*10); // wait for the event to happen wait_for_checkmark(MAX_WAIT); // evaluation EXPECT_EQ( 4, last_ordered_call); } /** * Schedules a delayed call and test whether the sleep time is acurate */ TEST_F(SystemQueueTimingTest, TimingSingleCall) { ptime now = microsec_clock::local_time(); sysq.scheduleCall( boost::bind(&SystemQueueTimingTest::TimeCheckingCall, this, now, DELAY_TIME*1000, DELAY_MARGIN), DELAY_TIME ); // main thread is going to sleep.. usleep(DELAY_TIME * 1000 + DELAY_MARGIN * 2); wait_for_checkmark(MAX_WAIT); // make sure the measurement function was called at all EXPECT_TRUE(checkmark) << "Measurement function was NOT RUN AT ALL!"; } /** * Like TimingSingleCall but tests whether the timings change when multiple events are scheduled. */ TEST_F(SystemQueueTimingTest, TimingMultipleCalls) { ptime now = microsec_clock::local_time(); sysq.scheduleCall( boost::bind(&SystemQueueTimingTest::TimeCheckingCall, this, now, DELAY_TIME*1000 * 2, DELAY_MARGIN), DELAY_TIME * 2 ); sysq.scheduleCall( boost::bind(&SystemQueueTimingTest::TimeCheckingCall, this, now, DELAY_TIME*1000 * 3, DELAY_MARGIN), DELAY_TIME * 3 ); sysq.scheduleCall( boost::bind(&SystemQueueTimingTest::TimeCheckingCall, this, now, DELAY_TIME*1000, DELAY_MARGIN), DELAY_TIME ); // main thread is going to sleep.. usleep(DELAY_TIME * 3000 + DELAY_MARGIN * 2); wait_for_checkmark(MAX_WAIT); // XXX wait_until_empty // make sure the measurement function was called at all EXPECT_EQ(3, calls) << "Not every event was performed.."; } /* * TODO * * maybe one more complicated testcase with timing and directly scheduled events * * but this probably only makes sense after a working SysQ implementation exists.. */