611 lines
17 KiB
C
611 lines
17 KiB
C
/*************************************************************************\
|
|
* Copyright (c) 2010 Brookhaven Science Associates, as Operator of
|
|
* Brookhaven National Laboratory.
|
|
* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne
|
|
* National Laboratory.
|
|
* SPDX-License-Identifier: EPICS
|
|
* EPICS BASE is distributed subject to a Software License Agreement found
|
|
* in file LICENSE that is included with this distribution.
|
|
\*************************************************************************/
|
|
/*
|
|
* Author: Michael Davidsaver
|
|
* Date: 2010-09-24
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "epicsAssert.h"
|
|
#include "epicsThread.h"
|
|
#include "epicsEvent.h"
|
|
#include "dbDefs.h"
|
|
#include "errlog.h"
|
|
#include "epicsUnitTest.h"
|
|
#include "testMain.h"
|
|
#include "iocLog.h"
|
|
#include "logClient.h"
|
|
#include "envDefs.h"
|
|
#include "osiSock.h"
|
|
#include "fdmgr.h"
|
|
#include "epicsString.h"
|
|
#include "errSymTbl.h"
|
|
|
|
/* private between errlog.c and this test */
|
|
LIBCOM_API
|
|
void errlogStripANSI(char *msg);
|
|
|
|
#define LOGBUFSIZE 2048
|
|
|
|
static
|
|
const char longmsg[]="A0123456789abcdef"
|
|
"B0123456789abcdef"
|
|
"C0123456789abcdef"
|
|
"D0123456789abcdef"
|
|
"E0123456789abcdef"
|
|
"F0123456789abcdef"
|
|
"G0123456789abcdef"
|
|
"H0123456789abcdef"
|
|
"I0123456789abcdef"
|
|
"J0123456789abcdef"
|
|
"K0123456789abcdef"
|
|
"L0123456789abcdef"
|
|
"M0123456789abcdef"
|
|
"N0123456789abcdef"
|
|
"O0123456789abcdef"
|
|
"P0123456789abcdef"
|
|
"Q0123456789abcdef"
|
|
"R0123456789abcdef"
|
|
"S0123456789abcdef"
|
|
;
|
|
STATIC_ASSERT(NELEMENTS(longmsg)==324);
|
|
|
|
static
|
|
const char truncmsg[]="A0123456789abcdef"
|
|
"B0123456789abcdef"
|
|
"C0123456789abcdef"
|
|
"D0123456789abcdef"
|
|
"E0123456789abcdef"
|
|
"F0123456789abcdef"
|
|
"G0123456789abcdef"
|
|
"H0123456789abcdef"
|
|
"I0123456789abcdef"
|
|
"J0123456789abcdef"
|
|
"K0123456789abcdef"
|
|
"L0123456789abcdef"
|
|
"M0123456789abcdef"
|
|
"N0123456789abcdef"
|
|
"O01<<TRUNCATED>>\n"
|
|
;
|
|
STATIC_ASSERT(NELEMENTS(truncmsg)==256);
|
|
|
|
typedef struct {
|
|
unsigned int count;
|
|
const char* expect;
|
|
size_t checkLen;
|
|
epicsEventId jammer;
|
|
int jam;
|
|
epicsEventId done;
|
|
} clientPvt;
|
|
|
|
static void testLogPrefix(void);
|
|
static void acceptNewClient( void *pParam );
|
|
static void readFromClient( void *pParam );
|
|
static void testPrefixLogandCompare( const char* logmessage);
|
|
|
|
static void *pfdctx;
|
|
static SOCKET sock;
|
|
static SOCKET insock;
|
|
|
|
static const char* prefixactualmsg[]= {
|
|
"A message without prefix",
|
|
"A message with prefix",
|
|
"DONE"
|
|
};
|
|
static const char *prefixstring = "fac=LI21 ";
|
|
static const char prefixexpectedmsg[] = "A message without prefix"
|
|
"fac=LI21 A message with prefix"
|
|
"fac=LI21 DONE"
|
|
;
|
|
static char prefixmsgbuffer[1024];
|
|
|
|
|
|
static void
|
|
testEqInt_(int line, int lhs, int rhs, const char *LHS, const char *RHS)
|
|
{
|
|
testOk(lhs==rhs, "%d: %s (%d) == %s (%d)", line, LHS, lhs, RHS, rhs);
|
|
}
|
|
#define testEqInt(L, R) testEqInt_(__LINE__, L, R, #L, #R);
|
|
|
|
static
|
|
void logClient(void* raw, const char* msg)
|
|
{
|
|
clientPvt *pvt = raw;
|
|
size_t len;
|
|
char show[46];
|
|
|
|
/* Simulate thread priority on non-realtime
|
|
* OSs like Linux. This will cause the logging
|
|
* thread to sleep with the buffer lock held.
|
|
*/
|
|
if (pvt->jam > 0) {
|
|
pvt->jam = 0;
|
|
epicsEventMustWait(pvt->jammer);
|
|
} else if (pvt->jam < 0) {
|
|
pvt->jam++;
|
|
if (pvt->jam == 0)
|
|
epicsEventMustWait(pvt->jammer);
|
|
}
|
|
|
|
len = strlen(msg);
|
|
if (len > 45) {
|
|
/* Only show start and end of long messages */
|
|
strncpy(show, msg, 20);
|
|
show[20] = 0;
|
|
strcat(show + 20, " ... ");
|
|
strcat(show + 25, msg + len - 20);
|
|
}
|
|
else {
|
|
strcpy(show, msg);
|
|
}
|
|
|
|
if (pvt->checkLen)
|
|
if (!testOk(pvt->checkLen == len, "Received %d chars", (int) len)) {
|
|
testDiag("Expected %d", (int) pvt->checkLen);
|
|
if (!pvt->expect)
|
|
testDiag("Message is \"%s\"", show);
|
|
}
|
|
|
|
if (pvt->expect) {
|
|
if (!testOk(strcmp(pvt->expect, msg) == 0, "Message is \"%s\"", show)) {
|
|
len = strlen(pvt->expect);
|
|
if (len > 45) {
|
|
testDiag("Expected \"%.20s ... %s\"",
|
|
pvt->expect, pvt->expect + len - 20);
|
|
}
|
|
else {
|
|
testDiag("Expected \"%s\"", pvt->expect);
|
|
}
|
|
}
|
|
}
|
|
|
|
pvt->count++;
|
|
epicsEventSignal(pvt->done);
|
|
}
|
|
|
|
static
|
|
void testANSIStrip(void)
|
|
{
|
|
char scratch[64];
|
|
char actual[128];
|
|
char input[128];
|
|
#define testEscape(INP, EXPECT) \
|
|
strcpy(scratch, INP); \
|
|
epicsStrnEscapedFromRaw(input, sizeof(input), INP, sizeof(INP)); \
|
|
errlogStripANSI(scratch); \
|
|
epicsStrnEscapedFromRaw(actual, sizeof(actual), scratch, epicsStrnLen(scratch, sizeof(scratch))); \
|
|
testOk(strcmp(scratch, EXPECT)==0, "input \"%s\" expect \"%s\" actual \"%s\"", input, EXPECT, actual)
|
|
|
|
testEscape("", "");
|
|
testEscape("hello", "hello");
|
|
testEscape("he\033[31;1mllo", "hello");
|
|
testEscape("\033[31;1mhello", "hello");
|
|
testEscape("hello\033[31;1m", "hello");
|
|
testEscape("hello\033[31;1", "hello");
|
|
testEscape("hello\033[", "hello");
|
|
testEscape("hello\033", "hello");
|
|
|
|
#undef testEscape
|
|
}
|
|
|
|
static void testErrorMessageMatches(long status, const char *expected)
|
|
{
|
|
const char *msg = errSymMsg(status);
|
|
testOk(strcmp(msg, expected) == 0,
|
|
"Error code %ld returns \"%s\", expected message \"%s\"", status,
|
|
msg, expected
|
|
);
|
|
}
|
|
|
|
static void testGettingExistingErrorSymbol()
|
|
{
|
|
testErrorMessageMatches(S_err_invCode, "Invalid error symbol code");
|
|
}
|
|
|
|
static void testGettingNonExistingErrorSymbol()
|
|
{
|
|
long invented_code = (0x7999 << 16) | 0x9998;
|
|
testErrorMessageMatches(invented_code, "<Unknown code>");
|
|
}
|
|
|
|
static void testAddingErrorSymbol()
|
|
{
|
|
long invented_code = (0x7999 << 16) | 0x9999;
|
|
errSymbolAdd(invented_code, "Invented Error Message");
|
|
testErrorMessageMatches(invented_code, "Invented Error Message");
|
|
}
|
|
|
|
static void testAddingInvalidErrorSymbol()
|
|
{
|
|
long invented_code = (500 << 16) | 0x1;
|
|
testOk(errSymbolAdd(invented_code, "No matter"),
|
|
"Adding error symbol with module code < 501 should fail");
|
|
}
|
|
|
|
static void testAddingExistingErrorSymbol()
|
|
{
|
|
testOk(errSymbolAdd(S_err_invCode, "Duplicate"),
|
|
"Adding an error symbol with an existing error code should fail");
|
|
}
|
|
|
|
static void testAddingExistingErrorSymbolWithSameMessage()
|
|
{
|
|
long invented_code = (0x7999 << 16) | 0x9997;
|
|
errSymbolAdd(invented_code, "Invented Error Message");
|
|
testOk(!errSymbolAdd(invented_code, "Invented Error Message"),
|
|
"Adding identical error symbol shouldn't fail");
|
|
}
|
|
|
|
MAIN(epicsErrlogTest)
|
|
{
|
|
size_t mlen, i, N;
|
|
char msg[256];
|
|
clientPvt pvt, pvt2;
|
|
|
|
testPlan(54);
|
|
|
|
testANSIStrip();
|
|
|
|
strcpy(msg, truncmsg);
|
|
|
|
errlogInit2(LOGBUFSIZE, 256);
|
|
|
|
pvt.count = 0;
|
|
pvt2.count = 0;
|
|
|
|
pvt.expect = NULL;
|
|
pvt2.expect = NULL;
|
|
|
|
pvt.checkLen = 0;
|
|
pvt2.checkLen = 0;
|
|
|
|
pvt.jam = 0;
|
|
pvt2.jam = 0;
|
|
|
|
pvt.jammer = epicsEventMustCreate(epicsEventEmpty);
|
|
pvt.done = epicsEventMustCreate(epicsEventEmpty);
|
|
pvt2.jammer = epicsEventMustCreate(epicsEventEmpty);
|
|
pvt2.done = epicsEventMustCreate(epicsEventEmpty);
|
|
|
|
testDiag("Check listener registration");
|
|
|
|
errlogAddListener(&logClient, &pvt);
|
|
|
|
pvt.expect = "Testing";
|
|
pvt.checkLen = strlen(pvt.expect);
|
|
|
|
errlogPrintfNoConsole("%s", pvt.expect);
|
|
errlogFlush();
|
|
|
|
epicsEventMustWait(pvt.done);
|
|
testEqInt(pvt.count, 1);
|
|
|
|
errlogAddListener(&logClient, &pvt2);
|
|
|
|
/* logClient will not see ANSI escape sequences */
|
|
pvt2.expect = pvt.expect = "Testing2";
|
|
pvt2.checkLen = pvt.checkLen = strlen(pvt.expect);
|
|
|
|
errlogPrintfNoConsole("%s", ANSI_RED("Testing2"));
|
|
errlogFlush();
|
|
|
|
epicsEventMustWait(pvt.done);
|
|
testEqInt(pvt.count, 2);
|
|
|
|
epicsEventMustWait(pvt2.done);
|
|
testEqInt(pvt2.count, 1);
|
|
|
|
/* Removes the first listener */
|
|
testOk(1 == errlogRemoveListeners(&logClient, &pvt),
|
|
"Removed 1 listener");
|
|
|
|
pvt2.expect = "Testing3";
|
|
pvt2.checkLen = strlen(pvt2.expect);
|
|
|
|
errlogPrintfNoConsole("%s", pvt2.expect);
|
|
errlogFlush();
|
|
|
|
testOk(epicsEventWaitWithTimeout(pvt.done, 0.5) == epicsEventWaitTimeout,
|
|
"%d: Listener 1 didn't run", __LINE__);
|
|
testOk(epicsEventTryWait(pvt2.done) == epicsEventOK,
|
|
"%d: Listener 2 ran", __LINE__);
|
|
testEqInt(pvt.count, 2);
|
|
testEqInt(pvt2.count, 2);
|
|
|
|
/* Add the second listener again, then remove both instances */
|
|
errlogAddListener(&logClient, &pvt2);
|
|
testOk(2 == errlogRemoveListeners(&logClient, &pvt2),
|
|
"Removed 2 listeners");
|
|
|
|
errlogPrintfNoConsole("Something different");
|
|
errlogFlush();
|
|
|
|
testOk(epicsEventWaitWithTimeout(pvt.done, 0.5) == epicsEventWaitTimeout,
|
|
"%d: Listener 1 didn't run", __LINE__);
|
|
testOk(epicsEventTryWait(pvt2.done) == epicsEventWaitTimeout,
|
|
"%d: Listener 2 didn't run", __LINE__);
|
|
testEqInt(pvt.count, 2);
|
|
testEqInt(pvt2.count, 2);
|
|
|
|
/* Re-add one listener */
|
|
errlogAddListener(&logClient, &pvt);
|
|
|
|
testDiag("Check truncation");
|
|
|
|
pvt.expect = truncmsg;
|
|
pvt.checkLen = 255;
|
|
|
|
errlogPrintfNoConsole("%s", longmsg);
|
|
errlogFlush();
|
|
|
|
epicsEventMustWait(pvt.done);
|
|
testEqInt(pvt.count, 3);
|
|
|
|
pvt.expect = NULL;
|
|
|
|
testDiag("Check priority");
|
|
/* For the following tests it is important that
|
|
* the buffer should not flush until we request it
|
|
*/
|
|
pvt.jam = 1;
|
|
|
|
errlogPrintfNoConsole("%s", longmsg);
|
|
|
|
testOk(epicsEventWaitWithTimeout(pvt.done, 0.5) == epicsEventWaitTimeout,
|
|
"%d: Listener 1 didn't run", __LINE__);
|
|
testEqInt(pvt.count, 3);
|
|
|
|
epicsEventSignal(pvt.jammer);
|
|
errlogFlush();
|
|
|
|
epicsEventMustWait(pvt.done);
|
|
testEqInt(pvt.count, 4);
|
|
|
|
testDiag("Find buffer capacity (%u theoretical)",LOGBUFSIZE);
|
|
|
|
pvt.checkLen = 0;
|
|
|
|
for (mlen = 8; mlen <= 255; mlen *= 2) {
|
|
double eff;
|
|
char save = msg[mlen - 1];
|
|
|
|
N = LOGBUFSIZE / mlen; /* # of of messages to send */
|
|
msg[mlen - 1] = '\0';
|
|
pvt.count = 0;
|
|
/* pvt.checkLen = mlen - 1; */
|
|
|
|
pvt.jam = 1;
|
|
|
|
for (i = 0; i < N; i++) {
|
|
errlogPrintfNoConsole("%s", msg);
|
|
}
|
|
|
|
epicsEventSignal(pvt.jammer);
|
|
errlogFlush();
|
|
|
|
eff = (double) (pvt.count * mlen) / LOGBUFSIZE * 100.0;
|
|
testDiag(" For %d messages of length %d got %u (%.1f%% efficient)",
|
|
(int) N, (int) mlen, pvt.count, eff);
|
|
|
|
msg[mlen - 1] = save;
|
|
N = pvt.count; /* Save final count for the test below */
|
|
|
|
/* Clear "errlog: <n> messages were discarded" status */
|
|
pvt.checkLen = 0;
|
|
errlogPrintfNoConsole(".");
|
|
errlogFlush();
|
|
}
|
|
|
|
testOk(epicsEventTryWait(pvt.done) == epicsEventOK,
|
|
"%d: Listener 1 ran", __LINE__);
|
|
|
|
testDiag("Checking buffer use after partial flush");
|
|
|
|
/* Use the numbers from the largest block size above */
|
|
mlen /= 2;
|
|
msg[mlen - 1] = '\0';
|
|
|
|
pvt.jam = 1;
|
|
pvt.count = 0;
|
|
|
|
testDiag("Filling with %d messages of size %d", (int) N, (int) mlen);
|
|
|
|
for (i = 0; i < N; i++) {
|
|
errlogPrintfNoConsole("%s", msg);
|
|
}
|
|
|
|
testOk(epicsEventWaitWithTimeout(pvt.done, 0.5) == epicsEventWaitTimeout,
|
|
"%d: Listener 1 didn't run", __LINE__);
|
|
testEqInt(pvt.count, 0);
|
|
|
|
/* Extract the first 2 messages, 2*(sizeof(msgNode) + 128) bytes */
|
|
pvt.jam = -2;
|
|
epicsEventSignal(pvt.jammer);
|
|
epicsThreadSleep(0.5);
|
|
|
|
testDiag("Drained %u messages", pvt.count);
|
|
epicsEventMustWait(pvt.done);
|
|
testEqInt(pvt.count, 2);
|
|
|
|
/* The buffer has space for 1 more message: sizeof(msgNode) + 256 bytes */
|
|
errlogPrintfNoConsole("%s", msg); /* Use up that space */
|
|
|
|
testDiag("Overflow the buffer");
|
|
errlogPrintfNoConsole("%s", msg);
|
|
|
|
testOk(epicsEventWaitWithTimeout(pvt.done, 0.5) == epicsEventWaitTimeout,
|
|
"%d: Listener 1 didn't run", __LINE__);
|
|
testEqInt(pvt.count, 2);
|
|
|
|
epicsEventSignal(pvt.jammer); /* Empty */
|
|
errlogFlush();
|
|
|
|
testDiag("Logged %u messages", pvt.count);
|
|
epicsEventMustWait(pvt.done);
|
|
/* Expect N+1 messages +- 1 depending on impl */
|
|
testOk(pvt.count >= N && pvt.count<=N+2, "Logged %u messages, expected %zu", pvt.count, N+1);
|
|
|
|
/* Clean up */
|
|
testOk(1 == errlogRemoveListeners(&logClient, &pvt),
|
|
"Removed 1 listener");
|
|
|
|
osiSockAttach();
|
|
testLogPrefix();
|
|
osiSockRelease();
|
|
|
|
testGettingExistingErrorSymbol();
|
|
testGettingNonExistingErrorSymbol();
|
|
testAddingErrorSymbol();
|
|
testAddingInvalidErrorSymbol();
|
|
testAddingExistingErrorSymbol();
|
|
testAddingExistingErrorSymbolWithSameMessage();
|
|
|
|
return testDone();
|
|
}
|
|
/*
|
|
* Tests the log prefix code
|
|
* The prefix is only applied to log messages as they go out to the socket,
|
|
* so we need to create a server listening on a port, accept connections etc.
|
|
* This code is a reduced version of the code in iocLogServer.
|
|
*/
|
|
static void testLogPrefix(void) {
|
|
struct sockaddr_in serverAddr;
|
|
int status;
|
|
struct timeval timeout;
|
|
struct sockaddr_in actualServerAddr;
|
|
osiSocklen_t actualServerAddrSize;
|
|
char portstring[16];
|
|
|
|
|
|
testDiag("Testing iocLogPrefix");
|
|
|
|
timeout.tv_sec = 5; /* in seconds */
|
|
timeout.tv_usec = 0;
|
|
|
|
memset(prefixmsgbuffer, 0, sizeof prefixmsgbuffer);
|
|
|
|
/* Clear "errlog: <n> messages were discarded" status */
|
|
errlogPrintfNoConsole(".");
|
|
errlogFlush();
|
|
|
|
sock = epicsSocketCreate(AF_INET, SOCK_STREAM, 0);
|
|
if (sock == INVALID_SOCKET) {
|
|
testAbort("epicsSocketCreate failed.");
|
|
}
|
|
|
|
/* We listen on a an available port. */
|
|
memset(&serverAddr, 0, sizeof serverAddr);
|
|
serverAddr.sin_family = AF_INET;
|
|
serverAddr.sin_port = htons(0);
|
|
|
|
status = bind (sock, (struct sockaddr *)&serverAddr,
|
|
sizeof (serverAddr) );
|
|
if (status < 0) {
|
|
testAbort("bind failed; all ports in use?");
|
|
}
|
|
|
|
status = listen(sock, 10);
|
|
if (status < 0) {
|
|
testAbort("listen failed!");
|
|
}
|
|
|
|
/* Determine the port that the OS chose */
|
|
actualServerAddrSize = sizeof actualServerAddr;
|
|
memset(&actualServerAddr, 0, sizeof serverAddr);
|
|
status = getsockname(sock, (struct sockaddr *) &actualServerAddr,
|
|
&actualServerAddrSize);
|
|
if (status < 0) {
|
|
testAbort("Can't find port number!");
|
|
}
|
|
|
|
sprintf(portstring, "%d", ntohs(actualServerAddr.sin_port));
|
|
testDiag("Listening on port %s", portstring);
|
|
|
|
/* Set the EPICS environment variables for logging. */
|
|
epicsEnvSet ( "EPICS_IOC_LOG_INET", "localhost" );
|
|
epicsEnvSet ( "EPICS_IOC_LOG_PORT", portstring );
|
|
|
|
pfdctx = fdmgr_init();
|
|
if (status < 0) {
|
|
testAbort("fdmgr_init failed!");
|
|
}
|
|
|
|
status = fdmgr_add_callback(pfdctx, sock, fdi_read,
|
|
acceptNewClient, &serverAddr);
|
|
|
|
if (status < 0) {
|
|
testAbort("fdmgr_add_callback failed!");
|
|
}
|
|
|
|
testOk1(iocLogInit() == 0);
|
|
fdmgr_pend_event(pfdctx, &timeout);
|
|
|
|
testPrefixLogandCompare(prefixactualmsg[0]);
|
|
|
|
iocLogPrefix(prefixstring);
|
|
testPrefixLogandCompare(prefixactualmsg[1]);
|
|
testPrefixLogandCompare(prefixactualmsg[2]);
|
|
epicsSocketDestroy(sock);
|
|
}
|
|
|
|
static void testPrefixLogandCompare( const char* logmessage ) {
|
|
struct timeval timeout;
|
|
timeout.tv_sec = 5; /* in seconds */
|
|
timeout.tv_usec = 0;
|
|
|
|
errlogPrintfNoConsole("%s", logmessage);
|
|
errlogFlush();
|
|
iocLogFlush();
|
|
fdmgr_pend_event(pfdctx, &timeout);
|
|
}
|
|
|
|
static void acceptNewClient ( void *pParam )
|
|
{
|
|
osiSocklen_t addrSize;
|
|
struct sockaddr_in addr;
|
|
int status;
|
|
|
|
addrSize = sizeof ( addr );
|
|
insock = epicsSocketAccept ( sock, (struct sockaddr *)&addr, &addrSize );
|
|
testOk(insock != INVALID_SOCKET && addrSize >= sizeof (addr),
|
|
"Accepted new client");
|
|
|
|
status = fdmgr_add_callback(pfdctx, insock, fdi_read,
|
|
readFromClient, NULL);
|
|
|
|
testOk(status >= 0, "Client read configured");
|
|
}
|
|
|
|
static void readFromClient(void *pParam)
|
|
{
|
|
char recvbuf[1024];
|
|
int recvLength;
|
|
|
|
memset(recvbuf, 0, 1024);
|
|
recvLength = recv(insock, recvbuf, 1024, 0);
|
|
if (recvLength > 0) {
|
|
strcat(prefixmsgbuffer, recvbuf);
|
|
|
|
/* If we have received all of the messages. */
|
|
if (strstr(prefixmsgbuffer, "DONE") != NULL) {
|
|
size_t msglen = strlen(prefixexpectedmsg);
|
|
int prefixcmp = strncmp(prefixexpectedmsg, prefixmsgbuffer, msglen);
|
|
|
|
if (!testOk(prefixcmp == 0, "prefix matches")) {
|
|
testDiag("Expected '%s'\n", prefixexpectedmsg);
|
|
testDiag("Obtained '%s'\n", prefixmsgbuffer);
|
|
}
|
|
}
|
|
}
|
|
}
|