Skip to content

Commit

Permalink
add fdManager test
Browse files Browse the repository at this point in the history
  • Loading branch information
mdavidsaver committed Jan 11, 2025
1 parent dc846e8 commit 525d357
Show file tree
Hide file tree
Showing 3 changed files with 333 additions and 0 deletions.
5 changes: 5 additions & 0 deletions modules/libcom/test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ aslibtest_SRCS += aslibtest.c
testHarness_SRCS += aslibtest.c
TESTS += aslibtest

TESTPROD_HOST += fdManagerTest
fdManagerTest_SRCS += fdManagerTest.cpp
testHarness_SRCS += fdManagerTest.cpp
TESTS += fdManagerTest

# Perl module tests:
TESTS += macLib

Expand Down
2 changes: 2 additions & 0 deletions modules/libcom/test/epicsRunLibComTests.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ int initHookTest(void);
int ipAddrToAsciiTest(void);
int macDefExpandTest(void);
int macLibTest(void);
int fdManagerTest(void);
int osiSockTest(void);
int ringBytesTest(void);
int ringPointerTest(void);
Expand Down Expand Up @@ -110,6 +111,7 @@ void epicsRunLibComTests(void)
runTest(ipAddrToAsciiTest);
runTest(macDefExpandTest);
runTest(macLibTest);
runTest(fdManagerTest);
runTest(osiSockTest);
runTest(ringBytesTest);
runTest(ringPointerTest);
Expand Down
326 changes: 326 additions & 0 deletions modules/libcom/test/fdManagerTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
/*************************************************************************\
* 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 testNonBlock()
{
Socket s(AF_INET, SOCK_DGRAM);
set_non_blocking(s.sd);
char buf = '\0';
sockaddr_in src;
osiSocklen_t srclen = sizeof(src);
int ret = ::recvfrom(s.sd, &buf, 1, 0, (sockaddr*)&src, &srclen);
int err = SOCKERRNO;
testOk(ret<0 && (err==SOCK_EWOULDBLOCK || err==SOCK_EINTR),
"non-blocking recvfrom() %d, %d", ret, err);
}

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(14);
osiSockAttach();
testNonBlock();
testEmpty();
testOnlyTimer();
testSockIO();
osiSockRelease();
return testDone();
}

0 comments on commit 525d357

Please sign in to comment.