add MonitorFIFO

This commit is contained in:
Michael Davidsaver
2018-05-22 13:04:36 -07:00
parent 1e04c91d3c
commit 5e887a6d02
6 changed files with 1455 additions and 2 deletions

View File

@ -27,6 +27,9 @@ TESTPROD_HOST += testServerContext
testServerContext_SRCS += testServerContext.cpp
TESTS += testServerContext
TESTPROD_HOST += testmonitorfifo
testmonitorfifo_SRCS += testmonitorfifo.cpp
TESTS += testmonitorfifo
PROD_HOST += testServer
testServer_SRCS += testServer.cpp

View File

@ -0,0 +1,781 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <vector>
#include <pv/pvUnitTest.h>
#include <testMain.h>
#include <epicsMutex.h>
#include <pv/pvAccess.h>
#include <pv/current_function.h>
#if __cplusplus>=201103L
#include <functional>
#include <initializer_list>
namespace pvd = epics::pvData;
namespace pva = epics::pvAccess;
typedef epicsGuard<epicsMutex> Guard;
typedef epicsGuardRelease<epicsMutex> UnGuard;
namespace {
struct Tester {
// we only have one thread, so no need for sync.
enum cb_t {
Connect,
Event,
Unlisten,
Close,
LowWater,
};
static const char* name(cb_t cb) {
switch(cb) {
#define CASE(NAME) case NAME: return #NAME
CASE(Connect);
CASE(Event);
CASE(Unlisten);
CASE(Close);
CASE(LowWater);
default: return "???";
}
}
typedef std::vector<cb_t> timeline_t;
static timeline_t timeline;
struct Requester : public pva::MonitorRequester {
POINTER_DEFINITIONS(Requester);
epicsMutex mutex; // not strictly needed, but a good simulation of usage
virtual ~Requester() {}
virtual std::string getRequesterName() OVERRIDE FINAL {return "Tester::Requester";}
virtual void channelDisconnect(bool destroy) OVERRIDE FINAL {
testDiag("In %s", CURRENT_FUNCTION);
Guard G(mutex);
Tester::timeline.push_back(Close);
}
virtual void monitorConnect(epics::pvData::Status const & status,
pva::MonitorPtr const & monitor, epics::pvData::StructureConstPtr const & structure) OVERRIDE FINAL {
testDiag("In %s", CURRENT_FUNCTION);
Guard G(mutex);
Tester::timeline.push_back(Connect);
}
virtual void monitorEvent(pva::MonitorPtr const & monitor) OVERRIDE FINAL {
testDiag("In %s", CURRENT_FUNCTION);
Guard G(mutex);
Tester::timeline.push_back(Event);
}
virtual void unlisten(pva::MonitorPtr const & monitor) OVERRIDE FINAL {
testDiag("In %s", CURRENT_FUNCTION);
Guard G(mutex);
Tester::timeline.push_back(Unlisten);
}
};
Requester::shared_pointer requester;
struct Handler : public pva::MonitorFIFO::Source {
POINTER_DEFINITIONS(Handler);
epicsMutex mutex; // not strictly needed, but a good simulation of usage
std::function<void(pva::MonitorFIFO *mon, size_t)> action;
virtual ~Handler() {}
virtual void freeHighMark(pva::MonitorFIFO *mon, size_t numEmpty) OVERRIDE FINAL {
testDiag("In %s", CURRENT_FUNCTION);
Guard G(mutex);
Tester::timeline.push_back(LowWater);
if(action)
action(mon, numEmpty);
}
};
Handler::weak_pointer handler;
void setAction(const std::function<void(pva::MonitorFIFO *mon, size_t)>& action) {
Handler::shared_pointer H(handler);
H->action = action;
}
pva::MonitorFIFO::shared_pointer mon;
pvd::StructureConstPtr type;
Tester(const pvd::PVStructure::const_shared_pointer& pvReq,
pva::MonitorFIFO::Config *conf)
{
Handler::shared_pointer H(new Handler);
handler = H;
requester.reset(new Requester);
mon.reset(new pva::MonitorFIFO(requester, pvReq, H, conf));
}
void reset() {
timeline.clear();
}
void testTimeline(std::initializer_list<cb_t> l) {
size_t i=0;
for(auto event : l) {
if(i >= timeline.size()) {
testFail("timeline ends early. Expected %s", name(event));
continue;
}
testOk(event==timeline[i], "%s == %s", name(event), name(timeline[i]));
i++;
}
for(; i<timeline.size(); i++) {
testFail("timeline ends late. Unexpected %s", name(timeline[i]));
}
if(i==0 && timeline.empty())
testPass("timeline no events as expected");
reset();
}
void connect(pvd::ScalarType t) {
testDiag("connect() with %s", pvd::ScalarTypeFunc::name(t));
type = pvd::getFieldCreate()->createFieldBuilder()
->add("value", t)
->createStructure();
mon->open(type);
}
void close() {
testDiag("close()");
type.reset();
mon->close();
}
template<typename T>
void post(T val)
{
testShow()<<"post("<<val<<")";
assert(!!type);
pvd::PVStructurePtr V(pvd::getPVDataCreate()->createPVStructure(type));
pvd::PVScalarPtr fld(V->getSubFieldT<pvd::PVScalar>("value"));
fld->putFrom(val);
pvd::BitSet changed;
changed.set(fld->getFieldOffset());
mon->post(*V, changed);
}
template<typename T>
void tryPost(T val, bool expect, bool force = false)
{
assert(!!type);
pvd::PVStructurePtr V(pvd::getPVDataCreate()->createPVStructure(type));
pvd::PVScalarPtr fld(V->getSubFieldT<pvd::PVScalar>("value"));
fld->putFrom(val);
pvd::BitSet changed;
changed.set(fld->getFieldOffset());
testEqual(mon->tryPost(*V, changed, pvd::BitSet(), force), expect)<<" value="<<val;
}
};
Tester::timeline_t Tester::timeline;
void testEmpty(pva::Monitor& mon)
{
pva::MonitorElement::Ref elem(mon);
testTrue(!elem)<<"Queue expected empty";
}
template<typename T>
void testPop(pva::Monitor& mon, T expected, bool overrun = false)
{
pva::MonitorElement::Ref elem(mon);
if(!elem) {
testFail("Queue unexpected empty");
return;
}
pvd::PVScalarPtr fld(elem->pvStructurePtr->getSubFieldT<pvd::PVScalar>("value"));
T actual(fld->getAs<T>());
bool changed = elem->changedBitSet->get(fld->getFieldOffset());
bool overran = elem->overrunBitSet->get(fld->getFieldOffset());
bool pop = actual==expected && changed && overran==overrun;
testTrue(pop)
<<" "<<actual<<" == "<<expected<<" changed="<<changed<<" "<<overran<<"=="<<overrun;
}
static const pvd::PVStructure::const_shared_pointer pvReqEmpty(pvd::getPVDataCreate()->createPVStructure(
pvd::getFieldCreate()->createFieldBuilder()
->createStructure()));
pvd::PVStructure::const_shared_pointer
pvReqPipelineBuild() {
pvd::PVStructure::shared_pointer ret(pvd::getPVDataCreate()->createPVStructure(
pvd::getFieldCreate()->createFieldBuilder()
->addNestedStructure("record")
->addNestedStructure("_options")
->add("pipeline", pvd::pvBoolean)
->add("queueSize", pvd::pvUInt)
->endNested()
->endNested()
->createStructure()));
ret->getSubFieldT<pvd::PVScalar>("record._options.pipeline")->putFrom<pvd::boolean>(true);
ret->getSubFieldT<pvd::PVScalar>("record._options.queueSize")->putFrom<pvd::uint32>(2);
return ret;
}
static const pvd::PVStructure::const_shared_pointer pvReqPipeline(pvReqPipelineBuild());
// Test the basic life cycle of a FIFO.
// post and pop single elements
void checkPlain()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
Tester tester(pvReqEmpty, 0);
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.mon->start();
tester.testTimeline({});
testEmpty(*tester.mon);
tester.post(5);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 5);
testEmpty(*tester.mon);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
// close() while FIFO not empty.
void checkAfterClose()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
Tester tester(pvReqEmpty, 0);
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.mon->start();
tester.testTimeline({});
testEmpty(*tester.mon);
tester.post(5);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Event, Tester::Close});
// FIFO not cleared by close
testPop(*tester.mon, 5);
testEmpty(*tester.mon);
tester.mon->notify();
tester.testTimeline({});
}
// close() while FIFO not empty, then re-open
void checkReOpenLost()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
Tester tester(pvReqEmpty, 0);
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.mon->start();
tester.testTimeline({});
testEmpty(*tester.mon);
tester.post(5);
tester.mon->stop();
tester.close();
tester.mon->notify();
// FIFO cleared by connect()
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.mon->start();
tester.testTimeline({Tester::Event, Tester::Close, Tester::Connect});
// update 5 was lost!
testEmpty(*tester.mon);
tester.mon->notify();
tester.testTimeline({});
}
// type change while FIFO is empty
void checkTypeChange()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
Tester tester(pvReqEmpty, 0);
tester.connect(pvd::pvInt);
tester.mon->start();
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.post(5);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 5);
testEmpty(*tester.mon);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
testDiag("Type change");
tester.connect(pvd::pvString);
tester.mon->start();
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.post(std::string("hello"));
tester.mon->notify();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, std::string("hello"));
testEmpty(*tester.mon);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
// post() until the FIFO is full, and keep going. Check overrun behavour
void checkFill()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
pva::MonitorFIFO::Config conf = {4, 2, 0};
Tester tester(pvReqEmpty, &conf);
testEqual(conf.actualCount, 2u);
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.mon->start();
testEqual(conf.actualCount, tester.mon->freeCount());
testShow()<<"Empty before "<<*tester.mon;
// fill up and overflow
tester.post(5);
testShow()<<"A5 "<<*tester.mon;
tester.post(6);
testShow()<<"Full "<<*tester.mon;
tester.post(7);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testShow()<<"Overrun1 "<<*tester.mon;
tester.post(8);
tester.mon->notify();
tester.testTimeline({});
testShow()<<"Overrun2 "<<*tester.mon;
testPop(*tester.mon, 5);
tester.testTimeline({});
testPop(*tester.mon, 6);
tester.testTimeline({Tester::LowWater});
testPop(*tester.mon, 8, true);
tester.testTimeline({});
testEmpty(*tester.mon);
testShow()<<"Empty again "<<*tester.mon;
// repeat
tester.post(15);
tester.post(16);
tester.post(17);
tester.post(18);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 15);
tester.testTimeline({});
testPop(*tester.mon, 16);
tester.testTimeline({Tester::LowWater});
testPop(*tester.mon, 18, true);
tester.testTimeline({});
testEmpty(*tester.mon);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
// post() until past full, then pop() and post() on a partially full queue
void checkSaturate()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
pva::MonitorFIFO::Config conf = {4, 2, 0};
Tester tester(pvReqEmpty, &conf);
testEqual(conf.actualCount, 2u);
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.mon->start();
testShow()<<"Empty before "<<*tester.mon;
// fill up and overflow
tester.post(5);
tester.post(6);
tester.post(7);
tester.post(8);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 5);
tester.testTimeline({});
tester.post(9);
tester.mon->notify();
tester.testTimeline({});
testPop(*tester.mon, 6);
tester.testTimeline({});
tester.post(10);
tester.mon->notify();
tester.testTimeline({});
testPop(*tester.mon, 8, true);
tester.testTimeline({});
tester.post(11);
tester.mon->notify();
tester.testTimeline({});
testShow()<<"Overrun2 "<<*tester.mon;
testPop(*tester.mon, 9);
tester.testTimeline({});
testPop(*tester.mon, 10);
tester.testTimeline({Tester::LowWater});
testPop(*tester.mon, 11);
tester.testTimeline({});
testEmpty(*tester.mon);
tester.testTimeline({});
testShow()<<"Empty again "<<*tester.mon;
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
// check handling of pipeline=true
void checkPipeline()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
pva::MonitorFIFO::Config conf = {4, 3, 0};
Tester tester(pvReqPipeline, &conf);
testEqual(conf.actualCount, 2u);
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.mon->start();
testEqual(tester.mon->freeCount(), 0u);
tester.mon->reportRemoteQueueStatus(2);
testEqual(tester.mon->freeCount(), 2u);
tester.testTimeline({Tester::LowWater});
// fill up and overflow
tester.post(5);
tester.post(6);
tester.post(7);
tester.post(8);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testEqual(tester.mon->freeCount(), 0u);
testPop(*tester.mon, 5);
testPop(*tester.mon, 6);
testEmpty(*tester.mon);
testDiag("ACK 2");
testShow()<<"Before ACK "<<*tester.mon;
tester.mon->reportRemoteQueueStatus(2);
testShow()<<"After ACK "<<*tester.mon;
tester.testTimeline({Tester::LowWater});
testEqual(tester.mon->freeCount(), 1u);
// still have the overflow'd element on the queue
testPop(*tester.mon, 8, true);
testEmpty(*tester.mon);
tester.mon->reportRemoteQueueStatus(1);
testEqual(tester.mon->freeCount(), 2u);
tester.testTimeline({});
testShow()<<"Empty before re-fill "<<*tester.mon;
// fill up and overflow
tester.post(15);
tester.post(16);
tester.post(17);
tester.post(18);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 15);
testPop(*tester.mon, 16);
testEmpty(*tester.mon);
tester.testTimeline({});
testDiag("ACK 1");
testShow()<<"Before ACK "<<*tester.mon;
tester.mon->reportRemoteQueueStatus(1);
testShow()<<"After ACK "<<*tester.mon;
tester.testTimeline({});
// 1 inuse, 1 returned, 1 empty
tester.post(19);
tester.post(20);
testPop(*tester.mon, 18, true);
testEmpty(*tester.mon);
tester.post(21);
testEmpty(*tester.mon);
testShow()<<"Before final ACK "<<*tester.mon;
tester.mon->reportRemoteQueueStatus(2);
tester.testTimeline({Tester::LowWater});
testPop(*tester.mon, 21, true);
testEmpty(*tester.mon);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
// test the spam counter (keeps FIFO full) with pipeline=true
void checkSpam()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
pva::MonitorFIFO::Config conf = {4, 3, 0};
Tester tester(pvReqPipeline, &conf);
pvd::uint32 cnt = 0;
tester.setAction([&tester, &cnt] (pva::MonitorFIFO *mon, size_t nfree) {
testDiag("Spamming %zu", nfree);
while(nfree--) {
testDiag("nfree=%zu %c", nfree, (nfree>0)?'T':'F');
tester.tryPost(cnt++, nfree>0);
}
});
tester.mon->setFreeHighMark(0); // run action when all buffers free
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
testEqual(conf.actualCount, 2u);
testDiag("prime the pump");
tester.mon->reportRemoteQueueStatus(conf.actualCount);
tester.mon->notify();
tester.testTimeline({Tester::LowWater});
testShow()<<"Before start() "<<*tester.mon<<"\n";
tester.mon->start();
testShow()<<"After start() "<<*tester.mon<<"\n";
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 0);
testPop(*tester.mon, 1);
testEmpty(*tester.mon);
tester.mon->reportRemoteQueueStatus(2);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 2);
testPop(*tester.mon, 3);
testEmpty(*tester.mon);
tester.mon->reportRemoteQueueStatus(1);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 4);
testEmpty(*tester.mon);
tester.mon->reportRemoteQueueStatus(2);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 5);
testPop(*tester.mon, 6);
testEmpty(*tester.mon);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
// check pipeline=true, watermark, and unlisten.
// a sequence with a definite end
void checkCountdown()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
pva::MonitorFIFO::Config conf = {4, 3, 0};
Tester tester(pvReqPipeline, &conf);
pvd::int32 cnt = 10;
tester.setAction([&tester, &cnt] (pva::MonitorFIFO *mon, size_t nfree) {
testDiag("Spamming %zu", nfree);
while(cnt >= 0 && nfree--) {
pvd::int32 c = cnt--;
testDiag("Count %d nfree=%zu %c", c, nfree, (nfree>0)?'T':'F');
tester.tryPost(c, nfree>0);
}
if(cnt<0) {
testDiag("Finished!");
tester.mon->finish();
}
});
tester.mon->setFreeHighMark(0.5); // run action() when one of two buffer elements is free
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
testEqual(conf.actualCount, 2u);
testDiag("prime the pump");
tester.mon->reportRemoteQueueStatus(conf.actualCount);
tester.mon->notify();
tester.testTimeline({Tester::LowWater});
tester.mon->start();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 10);
testPop(*tester.mon, 9);
testEmpty(*tester.mon);
tester.testTimeline({});
tester.mon->reportRemoteQueueStatus(2);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 8);
testPop(*tester.mon, 7);
testEmpty(*tester.mon);
tester.testTimeline({});
tester.mon->reportRemoteQueueStatus(1);
tester.testTimeline({}); // nothing happens, watermark not reached
tester.mon->reportRemoteQueueStatus(1);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 6);
testPop(*tester.mon, 5);
testEmpty(*tester.mon);
tester.testTimeline({});
tester.mon->reportRemoteQueueStatus(1);
tester.testTimeline({});
tester.mon->reportRemoteQueueStatus(1);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 4);
testPop(*tester.mon, 3);
testEmpty(*tester.mon);
tester.testTimeline({});
tester.mon->reportRemoteQueueStatus(2);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 2);
testPop(*tester.mon, 1);
testEmpty(*tester.mon);
tester.testTimeline({});
tester.mon->reportRemoteQueueStatus(2);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 0);
testEmpty(*tester.mon);
tester.testTimeline({Tester::Unlisten});
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
} // namespace
MAIN(testmonitorfifo)
{
testPlan(184);
checkPlain();
checkAfterClose();
checkReOpenLost();
checkTypeChange();
checkFill();
checkSaturate();
checkPipeline();
checkSpam();
checkCountdown();
return testDone();
}
#else /* c++11 */
MAIN(testmonitorfifo)
{
testPlan(1);
testSkip(1, "no c++11");
return testDone();
}
#endif /* c++11 */