add fdManager test
This commit is contained in:
@ -209,6 +209,11 @@ aslibtest_SRCS += aslibtest.c
|
|||||||
testHarness_SRCS += aslibtest.c
|
testHarness_SRCS += aslibtest.c
|
||||||
TESTS += aslibtest
|
TESTS += aslibtest
|
||||||
|
|
||||||
|
TESTPROD_HOST += fdManagerTest
|
||||||
|
fdManagerTest_SRCS += fdManagerTest.cpp
|
||||||
|
testHarness_SRCS += fdManagerTest.cpp
|
||||||
|
TESTS += fdManagerTest
|
||||||
|
|
||||||
# Perl module tests:
|
# Perl module tests:
|
||||||
TESTS += macLib
|
TESTS += macLib
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ int initHookTest(void);
|
|||||||
int ipAddrToAsciiTest(void);
|
int ipAddrToAsciiTest(void);
|
||||||
int macDefExpandTest(void);
|
int macDefExpandTest(void);
|
||||||
int macLibTest(void);
|
int macLibTest(void);
|
||||||
|
int fdManagerTest(void);
|
||||||
int osiSockTest(void);
|
int osiSockTest(void);
|
||||||
int ringBytesTest(void);
|
int ringBytesTest(void);
|
||||||
int ringPointerTest(void);
|
int ringPointerTest(void);
|
||||||
@ -110,6 +111,7 @@ void epicsRunLibComTests(void)
|
|||||||
runTest(ipAddrToAsciiTest);
|
runTest(ipAddrToAsciiTest);
|
||||||
runTest(macDefExpandTest);
|
runTest(macDefExpandTest);
|
||||||
runTest(macLibTest);
|
runTest(macLibTest);
|
||||||
|
runTest(fdManagerTest);
|
||||||
runTest(osiSockTest);
|
runTest(osiSockTest);
|
||||||
runTest(ringBytesTest);
|
runTest(ringBytesTest);
|
||||||
runTest(ringPointerTest);
|
runTest(ringPointerTest);
|
||||||
|
312
modules/libcom/test/fdManagerTest.cpp
Normal file
312
modules/libcom/test/fdManagerTest.cpp
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
/*************************************************************************\
|
||||||
|
* Copyright (c) 2025 Michael Davidsaver
|
||||||
|
* SPDX-License-Identifier: EPICS
|
||||||
|
* EPICS Base is distributed subject to a Software License Agreement found
|
||||||
|
* in file LICENSE that is included with this distribution.
|
||||||
|
\*************************************************************************/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <osiSock.h>
|
||||||
|
#include <fdManager.h>
|
||||||
|
#include <epicsTime.h>
|
||||||
|
#include <epicsAtomic.h>
|
||||||
|
#include <epicsThread.h>
|
||||||
|
|
||||||
|
#include <epicsUnitTest.h>
|
||||||
|
#include <testMain.h>
|
||||||
|
|
||||||
|
#if __cplusplus<201103L
|
||||||
|
# define final
|
||||||
|
# define override
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void set_non_blocking(SOCKET sd)
|
||||||
|
{
|
||||||
|
osiSockIoctl_t yes = true;
|
||||||
|
int status = socket_ioctl ( sd,
|
||||||
|
FIONBIO, & yes);
|
||||||
|
if(status)
|
||||||
|
testFail("set_non_blocking fails : %d", SOCKERRNO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAII for epicsTimer
|
||||||
|
struct ScopedTimer {
|
||||||
|
epicsTimer& timer;
|
||||||
|
ScopedTimer(epicsTimer& t) :timer(t) {}
|
||||||
|
~ScopedTimer() { timer.destroy(); }
|
||||||
|
};
|
||||||
|
struct ScopedFDReg {
|
||||||
|
fdReg * const reg;
|
||||||
|
ScopedFDReg(fdReg* reg) :reg(reg) {}
|
||||||
|
~ScopedFDReg() { reg->destroy(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// RAII for socket
|
||||||
|
struct Socket {
|
||||||
|
SOCKET sd;
|
||||||
|
Socket() :sd(INVALID_SOCKET) {}
|
||||||
|
explicit
|
||||||
|
Socket(SOCKET sd) :sd(sd) {}
|
||||||
|
Socket(int af, int type)
|
||||||
|
:sd(epicsSocketCreate(af, type, 0))
|
||||||
|
{
|
||||||
|
if(sd==INVALID_SOCKET)
|
||||||
|
testAbort("failed to allocate socket %d %d", af, type);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Socket(const Socket&);
|
||||||
|
Socket& operator=(const Socket&);
|
||||||
|
public:
|
||||||
|
~Socket() {
|
||||||
|
if(sd!=INVALID_SOCKET)
|
||||||
|
epicsSocketDestroy(sd);
|
||||||
|
}
|
||||||
|
void swap(Socket& o) {
|
||||||
|
std::swap(sd, o.sd);
|
||||||
|
}
|
||||||
|
sockaddr_in bind()
|
||||||
|
{
|
||||||
|
sockaddr_in addr;
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
|
||||||
|
if(::bind(sd, (sockaddr*)&addr, sizeof(addr)))
|
||||||
|
testAbort("Unable to bind lo : %d", SOCKERRNO);
|
||||||
|
|
||||||
|
sockaddr_in ret;
|
||||||
|
osiSocklen_t addrlen = sizeof(ret);
|
||||||
|
if(getsockname(sd, (sockaddr*)&ret, &addrlen))
|
||||||
|
testAbort("Unable to getsockname : %d", SOCKERRNO);
|
||||||
|
(void)addrlen;
|
||||||
|
|
||||||
|
if(listen(sd, 1))
|
||||||
|
testAbort("Unable to listen : %d", SOCKERRNO);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DoConnect final : public epicsThreadRunable {
|
||||||
|
const SOCKET sd;
|
||||||
|
sockaddr_in to;
|
||||||
|
DoConnect(SOCKET sd, const sockaddr_in& to)
|
||||||
|
:sd(sd)
|
||||||
|
,to(to)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void run() override final {
|
||||||
|
int err = connect(sd, (sockaddr*)&to, sizeof(to));
|
||||||
|
testOk(err==0, "connect() %d %d", err, SOCKERRNO);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DoAccept final : public epicsThreadRunable {
|
||||||
|
const SOCKET sd;
|
||||||
|
Socket peer;
|
||||||
|
sockaddr_in peer_addr;
|
||||||
|
DoAccept(SOCKET sd) :sd(sd) {}
|
||||||
|
void run() override final {
|
||||||
|
osiSocklen_t len(sizeof(peer_addr));
|
||||||
|
Socket temp(accept(sd, (sockaddr*)&peer_addr, &len));
|
||||||
|
if(temp.sd==INVALID_SOCKET)
|
||||||
|
testFail("accept() -> %d", SOCKERRNO);
|
||||||
|
temp.swap(peer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DoRead final : public epicsThreadRunable {
|
||||||
|
const SOCKET sd;
|
||||||
|
void* buf;
|
||||||
|
unsigned buflen;
|
||||||
|
int n;
|
||||||
|
DoRead(SOCKET sd, void* buf, unsigned buflen): sd(sd), buf(buf), buflen(buflen), n(0) {}
|
||||||
|
void run() override final {
|
||||||
|
n = recv(sd, (char*)buf, buflen, 0);
|
||||||
|
if(n<0)
|
||||||
|
testFail("read() -> %d, %d", n, SOCKERRNO);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DoWriteAll final : public epicsThreadRunable {
|
||||||
|
const SOCKET sd;
|
||||||
|
const void* buf;
|
||||||
|
unsigned buflen;
|
||||||
|
DoWriteAll(SOCKET sd, const void* buf, unsigned buflen): sd(sd), buf(buf), buflen(buflen) {}
|
||||||
|
void run() override final {
|
||||||
|
unsigned nsent = 0;
|
||||||
|
while(nsent<buflen) {
|
||||||
|
int n = send(sd, nsent+(char*)buf, buflen-nsent, 0);
|
||||||
|
if(n<0) {
|
||||||
|
testFail("send() -> %d, %d", n, SOCKERRNO);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nsent += n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Expire final : public epicsTimerNotify {
|
||||||
|
bool expired;
|
||||||
|
|
||||||
|
Expire() :expired(false) {}
|
||||||
|
virtual ~Expire() {}
|
||||||
|
virtual expireStatus expire(const epicsTime &) override final
|
||||||
|
{
|
||||||
|
if(!expired) {
|
||||||
|
expired = true;
|
||||||
|
testPass("expired");
|
||||||
|
} else {
|
||||||
|
testFail("re-expired?");
|
||||||
|
}
|
||||||
|
return noRestart;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Event final : public fdReg {
|
||||||
|
int* ready;
|
||||||
|
|
||||||
|
Event(fdManager& mgr, fdRegType evt, SOCKET sd, int* ready)
|
||||||
|
:fdReg(sd, evt, false, mgr)
|
||||||
|
,ready(ready)
|
||||||
|
{}
|
||||||
|
virtual ~Event() {}
|
||||||
|
|
||||||
|
virtual void callBack() override final {
|
||||||
|
epics::atomic::set(*ready, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OneShot final : public fdReg {
|
||||||
|
int *mask;
|
||||||
|
|
||||||
|
OneShot(fdManager& mgr, fdRegType evt, SOCKET sd, int *mask)
|
||||||
|
:fdReg(sd, evt, true, mgr)
|
||||||
|
,mask(mask)
|
||||||
|
{}
|
||||||
|
virtual ~OneShot() {
|
||||||
|
epics::atomic::add(*mask, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void callBack() override final {
|
||||||
|
epics::atomic::add(*mask, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void testEmpty()
|
||||||
|
{
|
||||||
|
fdManager empty;
|
||||||
|
empty.process(0.1); // ca-gateway always passes 0.01
|
||||||
|
testPass("Did nothing");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testOnlyTimer()
|
||||||
|
{
|
||||||
|
fdManager mgr;
|
||||||
|
Expire trig, never;
|
||||||
|
ScopedTimer trig_timer(mgr.createTimer()),
|
||||||
|
never_timer(mgr.createTimer());
|
||||||
|
epicsTime now(epicsTime::getCurrent());
|
||||||
|
trig_timer.timer.start(trig, now+0.1);
|
||||||
|
never_timer.timer.start(never, now+9999999.0);
|
||||||
|
mgr.process(0.2);
|
||||||
|
testOk1(trig.expired);
|
||||||
|
testOk1(!never.expired);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testSockIO()
|
||||||
|
{
|
||||||
|
fdManager mgr;
|
||||||
|
Socket listener(AF_INET, SOCK_STREAM);
|
||||||
|
set_non_blocking(listener.sd);
|
||||||
|
sockaddr_in servAddr(listener.bind());
|
||||||
|
|
||||||
|
Socket client(AF_INET, SOCK_STREAM);
|
||||||
|
Socket server;
|
||||||
|
// listen() / connect()
|
||||||
|
{
|
||||||
|
int readable = 0;
|
||||||
|
Event evt(mgr, fdrRead, listener.sd, &readable);
|
||||||
|
DoConnect conn(client.sd, servAddr);
|
||||||
|
epicsThread connector(conn, "connect", 0);
|
||||||
|
connector.start();
|
||||||
|
|
||||||
|
mgr.process(5.0);
|
||||||
|
|
||||||
|
testOk1(readable);
|
||||||
|
|
||||||
|
DoAccept acc(listener.sd);
|
||||||
|
acc.run();
|
||||||
|
server.swap(acc.peer);
|
||||||
|
}
|
||||||
|
set_non_blocking(server.sd);
|
||||||
|
// writeable
|
||||||
|
{
|
||||||
|
int mask = 0;
|
||||||
|
new OneShot(mgr, fdrWrite, server.sd, &mask);
|
||||||
|
|
||||||
|
mgr.process(5.0);
|
||||||
|
|
||||||
|
testOk(mask==3, "OneShot event mask %x", mask);
|
||||||
|
}
|
||||||
|
// read
|
||||||
|
{
|
||||||
|
const char msg[] = "testing";
|
||||||
|
int readable = 0;
|
||||||
|
Event evt(mgr, fdrRead, server.sd, &readable);
|
||||||
|
DoWriteAll op(client.sd, msg, sizeof(msg)-1);
|
||||||
|
epicsThread writer(op, "writer", 0);
|
||||||
|
writer.start();
|
||||||
|
|
||||||
|
mgr.process(5.0);
|
||||||
|
|
||||||
|
testOk1(readable);
|
||||||
|
|
||||||
|
char buf[sizeof(msg)] = "";
|
||||||
|
DoRead(server.sd, buf, sizeof(buf)-1).run();
|
||||||
|
buf[sizeof(buf)-1] = '\0';
|
||||||
|
testOk(strcmp(msg, buf)==0, "%s == %s", msg, buf);
|
||||||
|
}
|
||||||
|
// timer while unreadable
|
||||||
|
{
|
||||||
|
|
||||||
|
int readable = 0;
|
||||||
|
Event evt(mgr, fdrRead, server.sd, &readable);
|
||||||
|
Expire tmo;
|
||||||
|
ScopedTimer timer(mgr.createTimer());
|
||||||
|
timer.timer.start(tmo, epicsTime::getCurrent()); // immediate
|
||||||
|
|
||||||
|
mgr.process(1.0);
|
||||||
|
|
||||||
|
testOk1(!readable);
|
||||||
|
testOk1(tmo.expired);
|
||||||
|
}
|
||||||
|
// notification on close()
|
||||||
|
{
|
||||||
|
int readable = 0;
|
||||||
|
Event evt(mgr, fdrRead, server.sd, &readable);
|
||||||
|
|
||||||
|
shutdown(client.sd, SHUT_RDWR);
|
||||||
|
//Socket().swap(client);
|
||||||
|
|
||||||
|
mgr.process(1.0);
|
||||||
|
|
||||||
|
testOk1(readable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
MAIN(fdManagerTest)
|
||||||
|
{
|
||||||
|
testPlan(13);
|
||||||
|
osiSockAttach();
|
||||||
|
testEmpty();
|
||||||
|
testOnlyTimer();
|
||||||
|
testSockIO();
|
||||||
|
osiSockRelease();
|
||||||
|
return testDone();
|
||||||
|
}
|
Reference in New Issue
Block a user