summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2017-05-10 15:04:18 -0400
committerNat Goodspeed <nat@lindenlab.com>2017-05-10 15:04:18 -0400
commit9c66072cacae0b3b86a277780e3a19e94accd6bc (patch)
treebe16b4903034fcad814121cfeef586ed4defa33e
parent16e9e87d925d145e7464cef24c089b3d2726d32d (diff)
Add LLEventThrottle tests; actually *all* lleventfilter.cpp tests.
For some reason there wasn't an entry in indra/llcommon/CMakeLists.txt to run the tests in indra/llcommon/tests/lleventfilter_test.cpp. It seems likely that at some point it existed, since all previous tests built and ran successfully. In any case, (re-)add lleventfilter_test.cpp to the set of llcommon tests. Also alphabetize them to make it easier to find a particular test invocation. Also add new tests for LLEventThrottle. To support this, refactor the concrete LLEventThrottle class into LLEventThrottleBase containing all the tricky logic, with pure virtual methods for access to LLTimer and LLEventTimeout, and an LLEventThrottle subclass containing the LLTimer and LLEventTimeout instances and corresponding implementations of the new pure virtual methods. That permits us to introduce TestEventThrottle, an alternate subclass with dummy implementations of the methods related to LLTimer and LLEventTimeout. In particular, we can explicitly advance simulated realtime to simulate particular LLTimer and LLEventTimeout behaviors. Finally, introduce Concat, a test LLEventPump listener class whose function is to concatenate received string event data into a composite string so we can readily test for particular sequences of events.
-rw-r--r--indra/llcommon/CMakeLists.txt15
-rw-r--r--indra/llcommon/lleventfilter.cpp96
-rw-r--r--indra/llcommon/lleventfilter.h49
-rw-r--r--indra/llcommon/tests/listener.h11
-rw-r--r--indra/llcommon/tests/lleventfilter_test.cpp124
5 files changed, 262 insertions, 33 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 3493f80556..aa76a57f1d 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -324,26 +324,27 @@ if (LL_TESTS)
LL_ADD_INTEGRATION_TEST(lldeadmantimer "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llerror "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(lleventcoro "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(lleventfilter "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llframetimer "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llinstancetracker "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lltrace "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lluri "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llunits "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(lleventcoro "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}")
## llexception_test.cpp isn't a regression test, and doesn't need to be run
## every build. It's to help a developer make implementation choices about
diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp
index 87eb583cb6..ddd7d5547a 100644
--- a/indra/llcommon/lleventfilter.cpp
+++ b/indra/llcommon/lleventfilter.cpp
@@ -38,12 +38,18 @@
#include "llerror.h" // LL_ERRS
#include "llsdutil.h" // llsd_matches()
+/*****************************************************************************
+* LLEventFilter
+*****************************************************************************/
LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak):
LLEventStream(name, tweak),
mSource(source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1)))
{
}
+/*****************************************************************************
+* LLEventMatching
+*****************************************************************************/
LLEventMatching::LLEventMatching(const LLSD& pattern):
LLEventFilter("matching"),
mPattern(pattern)
@@ -64,6 +70,9 @@ bool LLEventMatching::post(const LLSD& event)
return LLEventStream::post(event);
}
+/*****************************************************************************
+* LLEventTimeoutBase
+*****************************************************************************/
LLEventTimeoutBase::LLEventTimeoutBase():
LLEventFilter("timeout")
{
@@ -153,6 +162,9 @@ bool LLEventTimeoutBase::running() const
return mMainloop.connected();
}
+/*****************************************************************************
+* LLEventTimeout
+*****************************************************************************/
LLEventTimeout::LLEventTimeout() {}
LLEventTimeout::LLEventTimeout(LLEventPump& source):
@@ -170,6 +182,9 @@ bool LLEventTimeout::countdownElapsed() const
return mTimer.hasExpired();
}
+/*****************************************************************************
+* LLEventBatch
+*****************************************************************************/
LLEventBatch::LLEventBatch(std::size_t size):
LLEventFilter("batch"),
mBatchSize(size)
@@ -208,19 +223,22 @@ void LLEventBatch::setSize(std::size_t size)
}
}
-LLEventThrottle::LLEventThrottle(F32 interval):
+/*****************************************************************************
+* LLEventThrottleBase
+*****************************************************************************/
+LLEventThrottleBase::LLEventThrottleBase(F32 interval):
LLEventFilter("throttle"),
mInterval(interval),
mPosts(0)
{}
-LLEventThrottle::LLEventThrottle(LLEventPump& source, F32 interval):
+LLEventThrottleBase::LLEventThrottleBase(LLEventPump& source, F32 interval):
LLEventFilter(source, "throttle"),
mInterval(interval),
mPosts(0)
{}
-void LLEventThrottle::flush()
+void LLEventThrottleBase::flush()
{
// flush() is a no-op unless there's something pending.
// Don't test mPending because there's no requirement that the consumer
@@ -228,11 +246,11 @@ void LLEventThrottle::flush()
if (mPosts)
{
mPosts = 0;
- mAlarm.cancel();
+ alarmCancel();
// This is not to set our alarm; we are not yet requesting
// any notification. This is just to track whether subsequent post()
// calls fall within this mInterval or not.
- mTimer.setTimerExpirySec(mInterval);
+ timerSet(mInterval);
// copy and clear mPending BEFORE posting to avoid weird circularity
// effects
LLSD pending = mPending;
@@ -241,12 +259,12 @@ void LLEventThrottle::flush()
}
}
-LLSD LLEventThrottle::pending() const
+LLSD LLEventThrottleBase::pending() const
{
return mPending;
}
-bool LLEventThrottle::post(const LLSD& event)
+bool LLEventThrottleBase::post(const LLSD& event)
{
// Always capture most recent post() event data. If caller wants to
// aggregate multiple events, let them retrieve pending() and modify
@@ -257,7 +275,7 @@ bool LLEventThrottle::post(const LLSD& event)
++mPosts;
// We reset mTimer on every flush() call to let us know if we're still
// within the same mInterval. So -- are we?
- F32 timeRemaining = mTimer.getRemainingTimeF32();
+ F32 timeRemaining = timerGetRemaining();
if (! timeRemaining)
{
// more than enough time has elapsed, immediately flush()
@@ -266,24 +284,24 @@ bool LLEventThrottle::post(const LLSD& event)
else
{
// still within mInterval of the last flush() call: have to defer
- if (! mAlarm.running())
+ if (! alarmRunning())
{
// timeRemaining tells us how much longer it will be until
// mInterval seconds since the last flush() call. At that time,
// flush() deferred events.
- mAlarm.actionAfter(timeRemaining, boost::bind(&LLEventThrottle::flush, this));
+ alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this));
}
}
return false;
}
-void LLEventThrottle::setInterval(F32 interval)
+void LLEventThrottleBase::setInterval(F32 interval)
{
F32 oldInterval = mInterval;
mInterval = interval;
// If we are not now within oldInterval of the last flush(), we're done:
// this will only affect behavior starting with the next flush().
- F32 timeRemaining = mTimer.getRemainingTimeF32();
+ F32 timeRemaining = timerGetRemaining();
if (timeRemaining)
{
// We are currently within oldInterval of the last flush(). Figure out
@@ -305,16 +323,60 @@ void LLEventThrottle::setInterval(F32 interval)
else
{
// immediately reset mTimer
- mTimer.setTimerExpirySec(timeRemaining);
+ timerSet(timeRemaining);
// and if mAlarm is running, reset that too
- if (mAlarm.running())
+ if (alarmRunning())
{
- mAlarm.actionAfter(timeRemaining, boost::bind(&LLEventThrottle::flush, this));
+ alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this));
}
}
}
}
+F32 LLEventThrottleBase::getDelay() const
+{
+ return timerGetRemaining();
+}
+
+/*****************************************************************************
+* LLEventThrottle implementation
+*****************************************************************************/
+LLEventThrottle::LLEventThrottle(F32 interval):
+ LLEventThrottleBase(interval)
+{}
+
+LLEventThrottle::LLEventThrottle(LLEventPump& source, F32 interval):
+ LLEventThrottleBase(source, interval)
+{}
+
+void LLEventThrottle::alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action)
+{
+ mAlarm.actionAfter(interval, action);
+}
+
+bool LLEventThrottle::alarmRunning() const
+{
+ return mAlarm.running();
+}
+
+void LLEventThrottle::alarmCancel()
+{
+ return mAlarm.cancel();
+}
+
+void LLEventThrottle::timerSet(F32 interval)
+{
+ mTimer.setTimerExpirySec(interval);
+}
+
+F32 LLEventThrottle::timerGetRemaining() const
+{
+ return mTimer.getRemainingTimeF32();
+}
+
+/*****************************************************************************
+* LLEventBatchThrottle
+*****************************************************************************/
LLEventBatchThrottle::LLEventBatchThrottle(F32 interval):
LLEventThrottle(interval)
{}
@@ -326,5 +388,7 @@ LLEventBatchThrottle::LLEventBatchThrottle(LLEventPump& source, F32 interval):
bool LLEventBatchThrottle::post(const LLSD& event)
{
// simply retrieve pending value and append the new event to it
- return LLEventThrottle::post(pending().append(event));
+ LLSD partial = pending();
+ partial.append(event);
+ return LLEventThrottle::post(partial);
}
diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h
index 68890846a7..cae18bfd86 100644
--- a/indra/llcommon/lleventfilter.h
+++ b/indra/llcommon/lleventfilter.h
@@ -247,7 +247,7 @@ private:
};
/**
- * LLEventThrottle: construct with a time interval. Regardless of how
+ * LLEventThrottleBase: construct with a time interval. Regardless of how
* frequently you call post(), LLEventThrottle will pass on an event to
* its listeners no more often than once per specified interval.
*
@@ -268,21 +268,24 @@ private:
* 17: post(): immediately passed to listeners, next no sooner than 20
*
* For a deferred event, the LLSD blob delivered to listeners is from the most
- * recent deferred post() call. However, you may obtain the previous event
- * blob by calling pending(), modify it however you want and post() the new
- * value. Each time an event is delivered to listeners, the pending() value is
- * reset to isUndefined().
+ * recent deferred post() call. However, a sender may obtain the previous
+ * event blob by calling pending(), modifying it as desired and post()ing the
+ * new value. Each time an event is delivered to listeners, the pending()
+ * value is reset to isUndefined().
*
* You may also call flush() to immediately pass along any deferred events to
* all listeners.
+ *
+ * @NOTE This is an abstract base class so that, for testing, we can use an
+ * alternate "timer" that doesn't actually consume real time.
*/
-class LL_COMMON_API LLEventThrottle: public LLEventFilter
+class LL_COMMON_API LLEventThrottleBase: public LLEventFilter
{
public:
// pass time interval
- LLEventThrottle(F32 interval);
+ LLEventThrottleBase(F32 interval);
// construct and connect
- LLEventThrottle(LLEventPump& source, F32 interval);
+ LLEventThrottleBase(LLEventPump& source, F32 interval);
// force out any deferred events
void flush();
@@ -301,7 +304,17 @@ public:
std::size_t getPostCount() const { return mPosts; }
// time until next event would be passed through, 0.0 if now
- F32 getDelay() const { return mTimer.getRemainingTimeF32(); }
+ F32 getDelay() const;
+
+protected:
+ // Implement these time-related methods for a valid LLEventThrottleBase
+ // subclass (see LLEventThrottle). For testing, we use a subclass that
+ // doesn't involve actual elapsed time.
+ virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) = 0;
+ virtual bool alarmRunning() const = 0;
+ virtual void alarmCancel() = 0;
+ virtual void timerSet(F32 interval) = 0;
+ virtual F32 timerGetRemaining() const = 0;
private:
// remember throttle interval
@@ -310,6 +323,24 @@ private:
std::size_t mPosts;
// pending event data from most recent deferred event
LLSD mPending;
+};
+
+/**
+ * Production implementation of LLEventThrottle.
+ */
+class LLEventThrottle: public LLEventThrottleBase
+{
+public:
+ LLEventThrottle(F32 interval);
+ LLEventThrottle(LLEventPump& source, F32 interval);
+
+private:
+ virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) override;
+ virtual bool alarmRunning() const override;
+ virtual void alarmCancel() override;
+ virtual void timerSet(F32 interval) override;
+ virtual F32 timerGetRemaining() const override;
+
// use this to arrange a deferred flush() call
LLEventTimeout mAlarm;
// use this to track whether we're within mInterval of last flush()
diff --git a/indra/llcommon/tests/listener.h b/indra/llcommon/tests/listener.h
index 9c5c18a150..6072060bb6 100644
--- a/indra/llcommon/tests/listener.h
+++ b/indra/llcommon/tests/listener.h
@@ -138,4 +138,15 @@ struct Collect
StringVec result;
};
+struct Concat
+{
+ bool operator()(const LLSD& event)
+ {
+ result += event.asString();
+ return false;
+ }
+ void clear() { result.clear(); }
+ std::string result;
+};
+
#endif /* ! defined(LL_LISTENER_H) */
diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp
index 2cdfb52f2f..712864bf63 100644
--- a/indra/llcommon/tests/lleventfilter_test.cpp
+++ b/indra/llcommon/tests/lleventfilter_test.cpp
@@ -70,6 +70,85 @@ private:
bool mElapsed;
};
+// Similar remarks about LLEventThrottle: we're actually testing the logic in
+// LLEventThrottleBase, dummying out the LLTimer and LLEventTimeout used by
+// the production LLEventThrottle class.
+class TestEventThrottle: public LLEventThrottleBase
+{
+public:
+ TestEventThrottle(F32 interval):
+ LLEventThrottleBase(interval),
+ mAlarmRemaining(-1),
+ mTimerRemaining(-1)
+ {}
+ TestEventThrottle(LLEventPump& source, F32 interval):
+ LLEventThrottleBase(source, interval),
+ mAlarmRemaining(-1),
+ mTimerRemaining(-1)
+ {}
+
+ /*----- implementation of LLEventThrottleBase timing functionality -----*/
+ virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) override
+ {
+ mAlarmRemaining = interval;
+ mAlarmAction = action;
+ }
+
+ virtual bool alarmRunning() const override
+ {
+ // decrementing to exactly 0 should mean the alarm fires
+ return mAlarmRemaining > 0;
+ }
+
+ virtual void alarmCancel() override
+ {
+ mAlarmRemaining = -1;
+ }
+
+ virtual void timerSet(F32 interval) override
+ {
+ mTimerRemaining = interval;
+ }
+
+ virtual F32 timerGetRemaining() const override
+ {
+ // LLTimer.getRemainingTimeF32() never returns negative; 0.0 means expired
+ return (mTimerRemaining > 0.0)? mTimerRemaining : 0.0;
+ }
+
+ /*------------------- methods for manipulating time --------------------*/
+ void alarmAdvance(F32 delta)
+ {
+ bool wasRunning = alarmRunning();
+ mAlarmRemaining -= delta;
+ if (wasRunning && ! alarmRunning())
+ {
+ mAlarmAction();
+ }
+ }
+
+ void timerAdvance(F32 delta)
+ {
+ // This simple implementation, like alarmAdvance(), completely ignores
+ // HOW negative mTimerRemaining might go. All that matters is whether
+ // it's negative. We trust that no test method in this source will
+ // drive it beyond the capacity of an F32. Seems like a safe assumption.
+ mTimerRemaining -= delta;
+ }
+
+ void advance(F32 delta)
+ {
+ // Advance the timer first because it has no side effects.
+ // alarmAdvance() might call flush(), which will need to see the
+ // change in the timer.
+ timerAdvance(delta);
+ alarmAdvance(delta);
+ }
+
+ F32 mAlarmRemaining, mTimerRemaining;
+ LLEventTimeoutBase::Action mAlarmAction;
+};
+
/*****************************************************************************
* TUT
*****************************************************************************/
@@ -116,7 +195,9 @@ namespace tut
listener0.listenTo(driver));
// Construct a pattern LLSD: desired Event must have a key "foo"
// containing string "bar"
- LLEventMatching filter(driver, LLSD().insert("foo", "bar"));
+ LLSD pattern;
+ pattern.insert("foo", "bar");
+ LLEventMatching filter(driver, pattern);
listener1.reset(0);
LLTempBoundListener temp2(
listener1.listenTo(filter));
@@ -285,6 +366,47 @@ namespace tut
mainloop.post(17);
check_listener("no timeout 3", listener0, LLSD(0));
}
+
+ template<> template<>
+ void filter_object::test<5>()
+ {
+ set_test_name("LLEventThrottle");
+ TestEventThrottle throttle(3);
+ Concat cat;
+ throttle.listen("concat", boost::ref(cat));
+
+ // (sequence taken from LLEventThrottleBase Doxygen comments)
+ // 1: post(): event immediately passed to listeners, next no sooner than 4
+ throttle.advance(1);
+ throttle.post("1");
+ ensure_equals("1", cat.result, "1"); // delivered immediately
+ // 2: post(): deferred: waiting for 3 seconds to elapse
+ throttle.advance(1);
+ throttle.post("2");
+ ensure_equals("2", cat.result, "1"); // "2" not yet delivered
+ // 3: post(): deferred
+ throttle.advance(1);
+ throttle.post("3");
+ ensure_equals("3", cat.result, "1"); // "3" not yet delivered
+ // 4: no post() call, but event delivered to listeners; next no sooner than 7
+ throttle.advance(1);
+ ensure_equals("4", cat.result, "13"); // "3" delivered
+ // 6: post(): deferred
+ throttle.advance(2);
+ throttle.post("6");
+ ensure_equals("6", cat.result, "13"); // "6" not yet delivered
+ // 7: no post() call, but event delivered; next no sooner than 10
+ throttle.advance(1);
+ ensure_equals("7", cat.result, "136"); // "6" delivered
+ // 12: post(): immediately passed to listeners, next no sooner than 15
+ throttle.advance(5);
+ throttle.post(";12");
+ ensure_equals("12", cat.result, "136;12"); // "12" delivered
+ // 17: post(): immediately passed to listeners, next no sooner than 20
+ throttle.advance(5);
+ throttle.post(";17");
+ ensure_equals("17", cat.result, "136;12;17"); // "17" delivered
+ }
} // namespace tut
/*****************************************************************************