forked from epics-base/epics-base
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
dc846e8
commit 525d357
Showing
3 changed files
with
333 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |