diff --git a/.tools/make-tar.sh b/.tools/make-tar.sh index 928006288..276d8e204 100755 --- a/.tools/make-tar.sh +++ b/.tools/make-tar.sh @@ -20,9 +20,9 @@ maybedie() { usage() { cat <&2 -usage: $0 [-v] [-s] [ []] +usage: $0 [-v] [-d] [ []] - may be any git revision spec. (tag, branch, or commit id). + may be any git revision spec. (tag or, using -d, branch or commit id). If provided, must end with ".tar", ".tar.gz" or ".tar.bz2". If is omitted, "base-.tar.gz" will be used. diff --git a/configure/CONFIG_COMMON b/configure/CONFIG_COMMON index 19b26c24c..742e54ae4 100644 --- a/configure/CONFIG_COMMON +++ b/configure/CONFIG_COMMON @@ -83,8 +83,9 @@ IOCS_APPL_TOP = $(INSTALL_ABSOLUTE) #------------------------------------------------------- # How to portably check the flags to make +makeflags := $(firstword $(filter-out -,$(filter-out --%,$(MAKEFLAGS)))) define checkflags - make-$1 = $(findstring $1,$(filter-out --%,$(MAKEFLAGS))) + make-$1 := $(findstring $1,$(makeflags)) endef # This is extensible to most single letter flags: $(foreach flag,s q, $(eval $(call checkflags,$(flag)))) diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index c16f274a6..dca3f98ee 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -9,12 +9,25 @@ important to read more than just the first section to understand everything that has changed in each release. The PVA submodules each have their own individual sets of release notes which -should also be read to understand what has changed since earlier releases. +should also be read to understand what has changed since earlier releases: + +- [normativeTypes](https://github.com/epics-base/normativeTypesCPP/blob/master/documentation/RELEASE_NOTES.md) +- [pvAccess](http://epics-base.github.io/pvAccessCPP/pvarelease_notes.html) +- [pvData](http://epics-base.github.io/pvDataCPP/release_notes.html) +- [pvDatabase](https://github.com/epics-base/pvDatabaseCPP/blob/master/documentation/RELEASE_NOTES.md) +- [pva2pva](https://epics-base.github.io/pva2pva/release_notes.html) +- [pvaClient](https://github.com/epics-base/pvaClientCPP/blob/master/documentation/RELEASE_NOTES.md) **This version of EPICS has not been released yet.** ## Changes made on the 7.0 branch since 7.0.7 +### Add `$EPICS_CLI_TIMEOUT` + +Add support for CA tools timeout from environment variable `$EPICS_CLI_TIMEOUT` +which sets the default the default timeout for `caget` et al. +The `-w` argument continues to take precedence. + ### Fixed leak from a non-EPICS thread on WIN32 On Windows targets, if a thread not created by `epicsThreadCreate*()` directly diff --git a/modules/ca/src/client/catime.c b/modules/ca/src/client/catime.c index 45c484e50..474ac525f 100644 --- a/modules/ca/src/client/catime.c +++ b/modules/ca/src/client/catime.c @@ -450,7 +450,7 @@ void timeIt ( tf *pfunc, ti *pItems, unsigned iterations, epicsTimeStamp end_time; epicsTimeStamp start_time; double delay; - unsigned inlineIter; + unsigned inlineIter = 0; epicsTimeGetCurrent ( &start_time ); (*pfunc) ( pItems, iterations, &inlineIter ); diff --git a/modules/ca/src/tools/caget.c b/modules/ca/src/tools/caget.c index e60f61922..d24fcd066 100644 --- a/modules/ca/src/tools/caget.c +++ b/modules/ca/src/tools/caget.c @@ -392,6 +392,8 @@ int main (int argc, char *argv[]) LINE_BUFFER(stdout); /* Configure stdout buffering */ + use_ca_timeout_env ( &caTimeout); + while ((opt = getopt(argc, argv, ":taicnhsSVe:f:g:l:#:d:0:w:p:F:")) != -1) { switch (opt) { case 'h': /* Print usage */ @@ -437,8 +439,8 @@ int main (int argc, char *argv[]) if(epicsScanDouble(optarg, &caTimeout) != 1) { fprintf(stderr, "'%s' is not a valid timeout value " - "- ignored. ('caget -h' for help.)\n", optarg); - caTimeout = DEFAULT_TIMEOUT; + "- ignored, using '%.1f'. ('caget -h' for help.)\n", + optarg, caTimeout); } break; case '#': /* Array count */ diff --git a/modules/ca/src/tools/cainfo.c b/modules/ca/src/tools/cainfo.c index 7fa4dfb45..1bedcf52e 100644 --- a/modules/ca/src/tools/cainfo.c +++ b/modules/ca/src/tools/cainfo.c @@ -141,6 +141,8 @@ int main (int argc, char *argv[]) LINE_BUFFER(stdout); /* Configure stdout buffering */ + use_ca_timeout_env ( &caTimeout); + while ((opt = getopt(argc, argv, ":nhVw:s:p:")) != -1) { switch (opt) { case 'h': /* Print usage */ @@ -150,11 +152,16 @@ int main (int argc, char *argv[]) printf( "\nEPICS Version %s, CA Protocol version %s\n", EPICS_VERSION_STRING, ca_version() ); return 0; case 'w': /* Set CA timeout value */ + /* + * epicsScanDouble is a macro defined as epicsParseDouble, + * (found in modules/libcom/src/misc) which will only + * change caTimeout here if it finds an acceptable value. + */ if(epicsScanDouble(optarg, &caTimeout) != 1) { fprintf(stderr, "'%s' is not a valid timeout value " - "- ignored. ('cainfo -h' for help.)\n", optarg); - caTimeout = DEFAULT_TIMEOUT; + "- ignored, using '%.1f'. ('cainfo -h' for help.)\n", + optarg, caTimeout); } break; case 's': /* ca_client_status interest level */ diff --git a/modules/ca/src/tools/camonitor.c b/modules/ca/src/tools/camonitor.c index b272bc17f..c2bd10a74 100644 --- a/modules/ca/src/tools/camonitor.c +++ b/modules/ca/src/tools/camonitor.c @@ -219,6 +219,8 @@ int main (int argc, char *argv[]) LINE_BUFFER(stdout); /* Configure stdout buffering */ + use_ca_timeout_env ( &caTimeout); + while ((opt = getopt(argc, argv, ":nhVm:sSe:f:g:l:#:0:w:t:p:F:")) != -1) { switch (opt) { case 'h': /* Print usage */ @@ -251,11 +253,16 @@ int main (int argc, char *argv[]) } break; case 'w': /* Set CA timeout value */ + /* + * epicsScanDouble is a macro defined as epicsParseDouble, + * (found in modules/libcom/src/misc) which will only + * change caTimeout here if it finds an acceptable value. + */ if(epicsScanDouble(optarg, &caTimeout) != 1) { fprintf(stderr, "'%s' is not a valid timeout value " - "- ignored. ('camonitor -h' for help.)\n", optarg); - caTimeout = DEFAULT_TIMEOUT; + "- ignored, using '%.1f'. ('camonitor -h' for help.)\n", + optarg, caTimeout); } break; case '#': /* Array count */ diff --git a/modules/ca/src/tools/caput.c b/modules/ca/src/tools/caput.c index 05247643a..3f368469c 100644 --- a/modules/ca/src/tools/caput.c +++ b/modules/ca/src/tools/caput.c @@ -284,6 +284,8 @@ int main (int argc, char *argv[]) LINE_BUFFER(stdout); /* Configure stdout buffering */ putenv("POSIXLY_CORRECT="); /* Behave correct on GNU getopt systems */ + use_ca_timeout_env ( &caTimeout); + while ((opt = getopt(argc, argv, ":cnlhatsVS#:w:p:F:")) != -1) { switch (opt) { case 'h': /* Print usage */ @@ -318,11 +320,16 @@ int main (int argc, char *argv[]) request = callback; break; case 'w': /* Set CA timeout value */ + /* + * epicsScanDouble is a macro defined as epicsParseDouble, + * (found in modules/libcom/src/misc) which will only + * change caTimeout here if it finds an acceptable value. + */ if(epicsScanDouble(optarg, &caTimeout) != 1) { fprintf(stderr, "'%s' is not a valid timeout value " - "- ignored. ('caput -h' for help.)\n", optarg); - caTimeout = DEFAULT_TIMEOUT; + "- ignored, using '%.1f'. ('caput -h' for help.)\n", + optarg, caTimeout); } break; case '#': /* Array count */ @@ -337,7 +344,7 @@ int main (int argc, char *argv[]) if (sscanf(optarg,"%u", &caPriority) != 1) { fprintf(stderr, "'%s' is not a valid CA priority " - "- ignored. ('caget -h' for help.)\n", optarg); + "- ignored. ('caput -h' for help.)\n", optarg); caPriority = DEFAULT_CA_PRIORITY; } if (caPriority > CA_PRIORITY_MAX) caPriority = CA_PRIORITY_MAX; diff --git a/modules/ca/src/tools/tool_lib.c b/modules/ca/src/tools/tool_lib.c index 6d4d971b6..58bd3d047 100644 --- a/modules/ca/src/tools/tool_lib.c +++ b/modules/ca/src/tools/tool_lib.c @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -53,7 +54,7 @@ char fieldSeparator = ' '; /* OFS default is whitespace */ int enumAsNr = 0; /* used for -n option - get DBF_ENUM as number */ int charArrAsStr = 0; /* used for -S option - treat char array as (long) string */ -double caTimeout = 1.0; /* wait time default (see -w option) */ +double caTimeout = DEFAULT_TIMEOUT; /* wait time default (see -w option) */ capri caPriority = DEFAULT_CA_PRIORITY; /* CA Priority */ #define TIMETEXTLEN 28 /* Length of timestamp text buffer */ @@ -639,3 +640,22 @@ int connect_pvs (pv* pvs, int nPvs) } return returncode; } + + +/* Set the timeout to EPICS_CLI_TIMEOUT */ +void use_ca_timeout_env ( double* timeout) +{ + const char* tmoStr; /* contents of environment var */ + + if ((tmoStr = getenv("EPICS_CLI_TIMEOUT")) != NULL && timeout != NULL) + { + if(epicsScanDouble(tmoStr, timeout) != 1) + { + fprintf(stderr, "'%s' is not a valid timeout value " + "(from 'EPICS_CLI_TIMEOUT' in the environment) - " + "ignored. (use '-h' for help.)\n", tmoStr); + *timeout = DEFAULT_TIMEOUT; + } + + } +} diff --git a/modules/ca/src/tools/tool_lib.h b/modules/ca/src/tools/tool_lib.h index 0933fc8c2..ed3d8b0cd 100644 --- a/modules/ca/src/tools/tool_lib.h +++ b/modules/ca/src/tools/tool_lib.h @@ -100,6 +100,7 @@ extern char *dbr2str (const void *value, unsigned type); extern void print_time_val_sts (pv *pv, unsigned long reqElems); extern int create_pvs (pv *pvs, int nPvs, caCh *pCB ); extern int connect_pvs (pv *pvs, int nPvs ); +extern void use_ca_timeout_env (double* timeout); /* * no additions below this endif diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c index a806f3cfa..fd682c93e 100644 --- a/modules/database/src/ioc/db/dbChannel.c +++ b/modules/database/src/ioc/db/dbChannel.c @@ -575,15 +575,10 @@ long dbChannelOpen(dbChannel *chan) } /* Set up type probe */ - probe.type = dbfl_type_val; - probe.ctx = dbfl_context_read; + memset(&probe, 0, sizeof(probe)); probe.field_type = dbChannelExportType(chan); probe.no_elements = dbChannelElements(chan); probe.field_size = dbChannelFieldSize(chan); - probe.sevr = NO_ALARM; - probe.stat = NO_ALARM; - probe.time.secPastEpoch = 0; - probe.time.nsec = 0; p = probe; diff --git a/modules/database/src/ioc/db/dbCommon.dbd.pod b/modules/database/src/ioc/db/dbCommon.dbd.pod index c8ae7e6ba..5366d477a 100644 --- a/modules/database/src/ioc/db/dbCommon.dbd.pod +++ b/modules/database/src/ioc/db/dbCommon.dbd.pod @@ -91,20 +91,25 @@ For example these rates are all valid: 1 second 2 Hertz -The B field specifies record processing at initialization. If it is set -to YES during database configuration, the record is processed once at IOC -initialization (before the normal scan tasks are started). +The B field specifies record processing at initialization. It can have the +values NO, YES, RUN, RUNNING, PAUSE, and PAUSED. If it is set to YES during +database configuration, the record is processed once at IOC initialization +(before the normal scan tasks are started). For the other values see +L for more details. -The B field orders the records within a specific SCAN group. This is not -meaningful for passive records. All records of a specified phase are processed -before those with higher phase number. It is generally better practice to use -linked passive records to enforce the order of processing rather than a phase -number. +The B field orders the records processed within a specific SCAN group or +PINI processing phase. All records of a specified phase are processed before +those with higher phase number. It is generally better practice to use linked +passive records to enforce the order of processing rather than a phase number. +If the PINI field is set to NO, the PHAS field is not meaningful for passive +records. -The B field specifies an event number. This event number is used if the -SCAN field is set to C. All records with scan type C and the -same EVNT value will be processed when a call to post_event for EVNT is made. -The call to post_event is: post_event(short event_number). +The B field specifies an event name or number. This event identifier is +used if the SCAN field is set to C. All records with scan type C +and the same EVNT value will be processed when that event is signalled by other +software running in the IOC, either by calling C for +numeric events, or by calling C with an event handle previously +looked up using C for named events. The B field specifies the scheduling priority for processing records with SCAN=C and asynchronous record completion tasks. diff --git a/modules/database/src/ioc/dbStatic/dbLexRoutines.c b/modules/database/src/ioc/dbStatic/dbLexRoutines.c index 0fd250826..85af1f110 100644 --- a/modules/database/src/ioc/dbStatic/dbLexRoutines.c +++ b/modules/database/src/ioc/dbStatic/dbLexRoutines.c @@ -1258,7 +1258,7 @@ static void dbAlias(char *name, char *alias) DBENTRY dbEntry; DBENTRY *pdbEntry = &dbEntry; - if(dbRecordNameValidate(alias)) + if(dbRecordNameValidate(alias) || dbRecordNameValidate(name)) return; dbInitEntry(savedPdbbase, pdbEntry); diff --git a/modules/database/src/std/rec/calcRecord.dbd.pod b/modules/database/src/std/rec/calcRecord.dbd.pod index 053ce85c5..23893feeb 100644 --- a/modules/database/src/std/rec/calcRecord.dbd.pod +++ b/modules/database/src/std/rec/calcRecord.dbd.pod @@ -460,12 +460,17 @@ conditions. The HYST field defines an alarm deadband for each limit. +The AFTC field sets the time constant on a low-pass filter that delays the +reporting of limit alarms until the signal has been within the alarm range for +that number of seconds (the default AFTC value of zero retains the previous +behavior). + See L for a complete explanation of record alarms and of the standard fields. L lists other fields related to alarms that are common to all record types. -=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST +=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST, AFTC =head3 Monitor Parameters @@ -496,7 +501,7 @@ manner for the VAL field. =cut - include "dbCommon.dbd" + include "dbCommon.dbd" field(VAL,DBF_DOUBLE) { prompt("Result") promptgroup("50 - Output") diff --git a/modules/database/src/std/rec/longinRecord.dbd.pod b/modules/database/src/std/rec/longinRecord.dbd.pod index 498cf61c6..94b8cf7f5 100644 --- a/modules/database/src/std/rec/longinRecord.dbd.pod +++ b/modules/database/src/std/rec/longinRecord.dbd.pod @@ -70,6 +70,15 @@ The possible alarm conditions for long inputs are the SCAN, READ, and limit alarms. The SCAN and READ alarms are called by the record or device support routines. +The HYST field controls hysteresis to prevent alarm chattering from an input +signal that is close to one of the limits and suffers from significant readout +noise. + +The AFTC field sets the time constant on a low-pass filter that delays the +reporting of limit alarms until the signal has been within the alarm range for +that number of seconds (the default AFTC value of zero retains the previous +behavior). + The limit alarms are configured by the user in the HIHI, LOLO, HIGH, and LOW fields using numerical values. For each of these fields, there is a corresponding severity field which can be either NO_ALARM, MINOR, or MAJOR. The @@ -77,7 +86,7 @@ HYST field can be used to specify a deadband around each limit. L lists the fields related to alarms that are common to all record types. -=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST +=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST, AFTC =head3 Monitor Parameters diff --git a/modules/database/src/std/rec/mbbiRecord.dbd.pod b/modules/database/src/std/rec/mbbiRecord.dbd.pod index a2c526074..b3c7628f3 100644 --- a/modules/database/src/std/rec/mbbiRecord.dbd.pod +++ b/modules/database/src/std/rec/mbbiRecord.dbd.pod @@ -405,6 +405,11 @@ and state alarms. The state alarms are configured in the below severity fields. These fields have the usual possible values for severity fields: NO_ALARM, MINOR, and MAJOR. +The AFTC field sets the time constant on a low-pass filter that delays the +reporting of limit alarms until the signal has been within the alarm range for +that number of seconds (the default AFTC value of zero retains the previous +behavior). + The unknown state severity (UNSV) field, if set to MINOR or MAJOR, triggers an alarm when the record support routine cannot find a matching value in the state value fields for C<<< rval >>>. @@ -420,7 +425,7 @@ for a complete explanation of record alarms and of the standard fields. L lists other fields related to alarms that are common to all record types. -=fields UNSV, COSV, ZRSV, ONSV, TWSV, THSV, FRSV, FVSV, SXSV, SVSV, EISV, NISV, TESV, ELSV, TVSV, TTSV, FTSV, FFSV +=fields UNSV, COSV, ZRSV, ONSV, TWSV, THSV, FRSV, FVSV, SXSV, SVSV, EISV, NISV, TESV, ELSV, TVSV, TTSV, FTSV, FFSV, AFTC =cut diff --git a/modules/database/src/std/rec/printfRecord.c b/modules/database/src/std/rec/printfRecord.c index 96bf63d2f..cf5e4d0c9 100644 --- a/modules/database/src/std/rec/printfRecord.c +++ b/modules/database/src/std/rec/printfRecord.c @@ -169,7 +169,7 @@ static void doPrintf(printfRecord *prec) precision = 0; if (ch == '%') { - added = epicsSnprintf(pval, vspace + 1, "%s", format); + added = epicsSnprintf(pval, vspace + 1, format); } else if (linkn++ >= PRINTF_NLINKS) { /* No more LNKn fields */ diff --git a/modules/database/test/ioc/db/Makefile b/modules/database/test/ioc/db/Makefile index 11f74f238..ad1d3d9b7 100644 --- a/modules/database/test/ioc/db/Makefile +++ b/modules/database/test/ioc/db/Makefile @@ -181,6 +181,8 @@ dbStaticTest_SRCS += dbStaticTest.c dbStaticTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp testHarness_SRCS += dbStaticTest.c TESTFILES += ../dbStaticTest.db +TESTFILES += ../dbStaticTestAlias1.db +TESTFILES += ../dbStaticTestAlias2.db TESTS += dbStaticTest # This runs all the test programs in a known working order: diff --git a/modules/database/test/ioc/db/dbStaticTest.c b/modules/database/test/ioc/db/dbStaticTest.c index 7cba3cec0..d118c790a 100644 --- a/modules/database/test/ioc/db/dbStaticTest.c +++ b/modules/database/test/ioc/db/dbStaticTest.c @@ -291,6 +291,18 @@ static void testDbVerify(const char *record) dbFinishEntry(&entry); } +static void testWrongAliasRecord(const char *filename) +{ + FILE *fp = NULL; + dbPath(pdbbase,"." OSI_PATH_LIST_SEPARATOR ".."); + dbOpenFile(pdbbase, filename, &fp); + if(!fp) { + testAbort("Unable to read %s", filename); + } + testOk(dbReadDatabaseFP(&pdbbase, fp, NULL, NULL) != 0, + "Wrong alias record in %s is expected to fail", filename); +} + void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); MAIN(dbStaticTest) @@ -298,13 +310,14 @@ MAIN(dbStaticTest) const char *ldir; FILE *fp = NULL; - testPlan(310); + testPlan(312); testdbPrepare(); testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); dbTestIoc_registerRecordDeviceDriver(pdbbase); dbPath(pdbbase,"." OSI_PATH_LIST_SEPARATOR ".."); - if(!(ldir = dbOpenFile(pdbbase, "dbStaticTest.db", &fp))) { + ldir = dbOpenFile(pdbbase, "dbStaticTest.db", &fp); + if(!fp) { testAbort("Unable to read dbStaticTest.db"); } if(dbReadDatabaseFP(&pdbbase, fp, NULL, NULL)) { @@ -312,6 +325,9 @@ MAIN(dbStaticTest) ldir, OSI_PATH_LIST_SEPARATOR); } + testWrongAliasRecord("dbStaticTestAlias1.db"); + testWrongAliasRecord("dbStaticTestAlias2.db"); + testEntry("testrec.VAL"); testEntry("testalias.VAL"); testEntry("testalias2.VAL"); diff --git a/modules/database/test/ioc/db/dbStaticTestAlias1.db b/modules/database/test/ioc/db/dbStaticTestAlias1.db new file mode 100644 index 000000000..20dc4e2f1 --- /dev/null +++ b/modules/database/test/ioc/db/dbStaticTestAlias1.db @@ -0,0 +1,4 @@ +record(x, "testrec2alias") { +} + +alias("testrec2alias.NAME", "testalias4") diff --git a/modules/database/test/ioc/db/dbStaticTestAlias2.db b/modules/database/test/ioc/db/dbStaticTestAlias2.db new file mode 100644 index 000000000..f36b66432 --- /dev/null +++ b/modules/database/test/ioc/db/dbStaticTestAlias2.db @@ -0,0 +1,4 @@ +record(x, "testrec2alias2") { +} + +alias("testrec2alias2", "testalias5.NAME") diff --git a/modules/database/test/std/rec/Makefile b/modules/database/test/std/rec/Makefile index 816164ebe..a717b769e 100644 --- a/modules/database/test/std/rec/Makefile +++ b/modules/database/test/std/rec/Makefile @@ -23,6 +23,11 @@ PROD_LIBS = dbRecStdTest dbRecStd dbCore ca Com TARGETS += $(COMMON_DIR)/recTestIoc.dbd DBDDEPENDS_FILES += recTestIoc.dbd$(DEP) recTestIoc_DBD = base.dbd +recTestIoc_DBD += bptTypeKdegC.dbd +recTestIoc_DBD += bptTypeKdegF.dbd +recTestIoc_DBD += bptTypeJdegF.dbd +recTestIoc_DBD += bptTypeJdegC.dbd + TESTFILES += $(COMMON_DIR)/recTestIoc.dbd testHarness_SRCS += recTestIoc_registerRecordDeviceDriver.cpp @@ -93,6 +98,34 @@ testHarness_SRCS += longoutTest.c TESTFILES += ../longoutTest.db TESTS += longoutTest +TESTPROD_HOST += boTest +boTest_SRCS += boTest.c +boTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += boTest.c +TESTFILES += ../boTest.db +TESTS += boTest + +TESTPROD_HOST += biTest +biTest_SRCS += biTest.c +biTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += biTest.c +TESTFILES += ../biTest.db +TESTS += biTest + +TESTPROD_HOST += printfTest +printfTest_SRCS += printfTest.c +printfTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += printfTest.c +TESTFILES += ../printfTest.db +TESTS += printfTest + +TESTPROD_HOST += aiTest +aiTest_SRCS += aiTest.c +aiTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += aiTest.c +TESTFILES += ../aiTest.db +TESTS += aiTest + TARGETS += $(COMMON_DIR)/asTestIoc.dbd DBDDEPENDS_FILES += asTestIoc.dbd$(DEP) asTestIoc_DBD += base.dbd diff --git a/modules/database/test/std/rec/aiTest.c b/modules/database/test/std/rec/aiTest.c new file mode 100644 index 000000000..887f56582 --- /dev/null +++ b/modules/database/test/std/rec/aiTest.c @@ -0,0 +1,445 @@ +/*************************************************************************\ +* Copyright (c) 2023 Karl Vestin +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "dbUnitTest.h" +#include "testMain.h" +#include "errlog.h" +#include "dbAccess.h" +#include "menuAlarmSevr.h" +#include "menuConvert.h" +#include "epicsMath.h" +#include "menuScan.h" +#include "caeventmask.h" + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static void test_soft_input(void){ + double value = 543.123; + + /* set soft channel */ + testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Soft Channel"); + testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL"); + testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec"); + + /* set VAL to on linked record */ + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, value); + + /* verify that this record VAL is updated but RVAL is not */ + testdbGetFieldEqual("test_ai_rec.VAL", DBF_DOUBLE, value); + testdbGetFieldEqual("test_ai_rec.RVAL", DBF_DOUBLE, 0.0); + + // number of tests = 6 +} + +static void test_raw_soft_input(void){ + double value = -255; + + /* set soft channel */ + testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel"); + testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL"); + testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec"); + + /* set VAL to on linked record */ + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, value); + + /* verify that this record RVAL and VAL are updated */ + testdbGetFieldEqual("test_ai_rec.VAL", DBF_DOUBLE, value); + testdbGetFieldEqual("test_ai_rec.RVAL", DBF_DOUBLE, value); + + // number of tests = 6 +} + +static void test_operator_display(void){ + const short hopr = 199; + const short lopr = 50; + const short prec = 2; + const char egu[] = "mm"; + const char desc[] = "hmm?"; + + /* set operator display parameters */ + testdbPutFieldOk("test_ai_rec.EGU", DBF_STRING, egu); + testdbPutFieldOk("test_ai_rec.DESC", DBF_STRING, desc); + testdbPutFieldOk("test_ai_rec.HOPR", DBF_SHORT, hopr); + testdbPutFieldOk("test_ai_rec.LOPR", DBF_SHORT, lopr); + testdbPutFieldOk("test_ai_rec.PREC", DBF_SHORT, prec); + + /* verify operator display parameters read back */ + testdbGetFieldEqual("test_ai_rec.NAME", DBF_STRING, "test_ai_rec"); + testdbGetFieldEqual("test_ai_rec.EGU", DBF_STRING, egu); + testdbGetFieldEqual("test_ai_rec.DESC", DBF_STRING, desc); + testdbGetFieldEqual("test_ai_rec.HOPR", DBF_SHORT, hopr); + testdbGetFieldEqual("test_ai_rec.LOPR", DBF_SHORT, lopr); + testdbGetFieldEqual("test_ai_rec.PREC", DBF_SHORT, prec); + + // number of tests = 7 +} + +static void test_no_linr_unit_conversion(void){ + const short roff = 10; + const short aslo = 2; + const short aoff = 4; + + const short rval = 9; + const short val = (((rval + roff) * aslo) + aoff); + + + /* set soft channel */ + testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel"); + testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL"); + testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec"); + + /* set unit conversion parameters */ + testdbPutFieldOk("test_ai_rec.ROFF", DBF_SHORT, roff); + testdbPutFieldOk("test_ai_rec.ASLO", DBF_SHORT, aslo); + testdbPutFieldOk("test_ai_rec.AOFF", DBF_SHORT, aoff); + testdbPutFieldOk("test_ai_rec.LINR", DBF_LONG, menuConvertNO_CONVERSION); + + /* verify conversion */ + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_SHORT, rval); + testdbGetFieldEqual("test_ai_rec.VAL", DBF_SHORT, val); + testdbGetFieldEqual("test_ai_rec.RVAL", DBF_SHORT, rval); + + // number of tests = 10 +} + +static void test_slope_linr_unit_conversion(void){ + const short roff = 1; + const short aslo = 3; + const short aoff = 99; + const short eslo = 3; + const short eoff = 2; + + const short rval = 7; + const short val = ((((rval + roff) * aslo) + aoff) * eslo) + eoff; + + + /* set soft channel */ + testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel"); + testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL"); + testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec"); + + /* set unit conversion parameters */ + testdbPutFieldOk("test_ai_rec.ROFF", DBF_LONG, roff); + testdbPutFieldOk("test_ai_rec.ASLO", DBF_LONG, aslo); + testdbPutFieldOk("test_ai_rec.AOFF", DBF_LONG, aoff); + testdbPutFieldOk("test_ai_rec.ESLO", DBF_LONG, eslo); + testdbPutFieldOk("test_ai_rec.EOFF", DBF_LONG, eoff); + testdbPutFieldOk("test_ai_rec.LINR", DBF_LONG, menuConvertSLOPE); + + /* verify conversion */ + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_LONG, rval); + testdbGetFieldEqual("test_ai_rec.VAL", DBF_LONG, val); + testdbGetFieldEqual("test_ai_rec.RVAL", DBF_LONG, rval); + + // number of tests = 12 +} + +static void test_linear_linr_unit_conversion(void){ + const long roff = 6; + const long aslo = 2; + const long aoff = 19; + const long eslo = 4; + const long eoff = 1; + const long egul = 9999; + const long eguf = -1000; + + const long rval = 2; + /* Since our raw soft input does not support EGUL and EGUF conversion LINEAR should work like SLOPE */ + const long val = ((((rval + roff) * aslo) + aoff) * eslo) + eoff; + + + /* set soft channel */ + testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel"); + testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL"); + testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec"); + + /* set unit conversion parameters */ + testdbPutFieldOk("test_ai_rec.ROFF", DBF_LONG, roff); + testdbPutFieldOk("test_ai_rec.ASLO", DBF_LONG, aslo); + testdbPutFieldOk("test_ai_rec.AOFF", DBF_LONG, aoff); + testdbPutFieldOk("test_ai_rec.ESLO", DBF_LONG, eslo); + testdbPutFieldOk("test_ai_rec.EOFF", DBF_LONG, eoff); + /* Since our raw soft input does not support EGUL and EGUF conversion we set them to lagre values here just to check they dont break anything */ + testdbPutFieldOk("test_ai_rec.EGUL", DBF_LONG, egul); + testdbPutFieldOk("test_ai_rec.EGUF", DBF_LONG, eguf); + testdbPutFieldOk("test_ai_rec.LINR", DBF_LONG, menuConvertLINEAR); + + /* verify conversion */ + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_LONG, rval); + testdbGetFieldEqual("test_ai_rec.VAL", DBF_LONG, val); + testdbGetFieldEqual("test_ai_rec.RVAL", DBF_LONG, rval); + + // number of tests = 14 +} + +static void test_bpt_conversion(void){ + const float typeKdegFin = 699.550510; + const float typeKdegF = 3.427551e+02; + const float typeKdegCin = 2902.787322; + const float typeKdegC = 7.028131e+02; + const float typeJdegFin = 1218.865107; + const float typeJdegF = 3.897504e+02; + const float typeJdegCin = 4042.988372; + const float typeJdegC = 6.918437e+02; + + /* set soft channel */ + testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel"); + testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL"); + testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec"); + + /* set unit conversion parameters */ + testdbPutFieldOk("test_ai_rec.ROFF", DBF_LONG, 0); + testdbPutFieldOk("test_ai_rec.ASLO", DBF_LONG, 1); + testdbPutFieldOk("test_ai_rec.AOFF", DBF_LONG, 0); + + /* Set type K deg F break point table and verify */ + testdbPutFieldOk("test_ai_rec.LINR", DBF_SHORT, menuConverttypeKdegF); + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_FLOAT, typeKdegFin); + testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, typeKdegF); + + /* Set type K deg C break point table and verify */ + testdbPutFieldOk("test_ai_rec.LINR", DBF_SHORT, menuConverttypeKdegC); + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_FLOAT, typeKdegCin); + testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, typeKdegC); + + /* Set type J deg F break point table and verify */ + testdbPutFieldOk("test_ai_rec.LINR", DBF_SHORT, menuConverttypeJdegF); + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_FLOAT, typeJdegFin); + testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, typeJdegF); + + /* Set type J deg C break point table and verify */ + testdbPutFieldOk("test_ai_rec.LINR", DBF_SHORT, menuConverttypeJdegC); + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_FLOAT, typeJdegCin); + testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, typeJdegC); + + // number of tests = 18 +} + +static void test_smoothing_filter(void){ + const double smoo = 0.7; + const short roff = 1; + const short aslo = 3; + const short aoff = -2; + const short eslo = 2; + const short eoff = 6; + + const short rval1 = 7; + const short val1 = ((((rval1 + roff) * aslo) + aoff) * eslo) + eoff; + const short rval2 = 9; + const short val2_pre_smoo = ((((rval2 + roff) * aslo) + aoff) * eslo) + eoff; + const float val2 = val1 * smoo + (1 - smoo) * val2_pre_smoo; + + + /* set soft channel */ + testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel"); + testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL"); + testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec"); + + /* set unit conversion parameters */ + testdbPutFieldOk("test_ai_rec.ROFF", DBF_LONG, roff); + testdbPutFieldOk("test_ai_rec.ASLO", DBF_LONG, aslo); + testdbPutFieldOk("test_ai_rec.AOFF", DBF_LONG, aoff); + testdbPutFieldOk("test_ai_rec.ESLO", DBF_LONG, eslo); + testdbPutFieldOk("test_ai_rec.EOFF", DBF_LONG, eoff); + testdbPutFieldOk("test_ai_rec.LINR", DBF_LONG, menuConvertSLOPE); + + /* set well known starting point (without smmothing) */ + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_SHORT, rval1); + testdbGetFieldEqual("test_ai_rec.VAL", DBF_SHORT, val1); + + /* set SMOO */ + testdbPutFieldOk("test_ai_rec.SMOO", DBF_DOUBLE, smoo); + + /* update value again and verify smoothing */ + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_SHORT, rval2); + testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, val2); + + /* clear SMOO */ + testdbPutFieldOk("test_ai_rec.SMOO", DBF_DOUBLE, 0.0); + + // number of tests = 15 +} + +static void test_udf(void){ + /* set soft channel */ + testdbPutFieldOk("test_ai_rec2.DTYP", DBF_STRING, "Raw Soft Channel"); + testdbPutFieldOk("test_ai_rec2.INP", DBF_STRING, "test_ai_link_rec2.VAL"); + testdbPutFieldOk("test_ai_link_rec2.FLNK", DBF_STRING, "test_ai_rec2"); + + /* verify UDF */ + testdbGetFieldEqual("test_ai_rec2.UDF", DBF_CHAR, TRUE); + + /* set VAL and verify UDF is cleared */ + testdbPutFieldOk("test_ai_link_rec2.VAL", DBF_FLOAT, 7.0); + testdbGetFieldEqual("test_ai_rec2.UDF", DBF_CHAR, FALSE); + + // number of tests = 6 +} + +static void test_alarm(void){ + const double h = 5000; + const double hh = 7500; + const double l = 200; + const double ll = -20; + const double hyst = 5; + + /* set soft channel */ + testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Soft Channel"); + testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL"); + testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec"); + + /* set alarm parameters */ + testdbPutFieldOk("test_ai_rec.HIGH", DBF_DOUBLE, h); + testdbPutFieldOk("test_ai_rec.HIHI", DBF_DOUBLE, hh); + testdbPutFieldOk("test_ai_rec.LOW", DBF_DOUBLE, l); + testdbPutFieldOk("test_ai_rec.LOLO", DBF_DOUBLE, ll); + testdbPutFieldOk("test_ai_rec.HHSV", DBF_SHORT, menuAlarmSevrMAJOR); + testdbPutFieldOk("test_ai_rec.HSV", DBF_SHORT, menuAlarmSevrMINOR); + testdbPutFieldOk("test_ai_rec.LSV", DBF_SHORT, menuAlarmSevrMINOR); + testdbPutFieldOk("test_ai_rec.LLSV", DBF_SHORT, menuAlarmSevrMAJOR); + + /* set non alarm VAL and verify */ + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, 400.0); + testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrNO_ALARM); + + /* set LOW alarm VAL and verify */ + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, 190.0); + testdbGetFieldEqual("test_ai_rec.VAL", DBF_DOUBLE, 190.0); + testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMINOR); + + /* set LOLO alarm VAL and verify */ + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, -9998.0); + testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR); + + /* set HIGH alarm VAL and verify */ + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, 6111.0); + testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMINOR); + + /* set HIHI alarm VAL and verify */ + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, 19998.0); + testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR); + + /* set HYST, verify and clear */ + testdbPutFieldOk("test_ai_rec.HYST", DBF_DOUBLE, hyst); + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, hh - 1.0); + testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR); + testdbPutFieldOk("test_ai_rec.HYST", DBF_DOUBLE, 0.0); + testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, hh - 2.0); + testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMINOR); + + /* verify LALM */ + testdbGetFieldEqual("test_ai_rec.LALM", DBF_DOUBLE, h); + + // number of tests = 29 +} + +static void test_aftc(void){ + const double h = 5000; + const double hh = 7500; + const double l = 200; + const double ll = -20; + const double aftc = 3; + testMonitor* test_mon = NULL; + epicsTimeStamp startTime; + epicsTimeStamp endTime; + double diffTime = 0; + + /* set soft channel */ + testdbPutFieldOk("test_ai_rec3.DTYP", DBF_STRING, "Raw Soft Channel"); + testdbPutFieldOk("test_ai_rec3.INP", DBF_STRING, "test_ai_link_rec3.VAL"); + testdbPutFieldOk("test_ai_link_rec3.FLNK", DBF_STRING, "test_ai_rec3"); + + /* set alarm parameters */ + testdbPutFieldOk("test_ai_rec3.HIGH", DBF_DOUBLE, h); + testdbPutFieldOk("test_ai_rec3.HIHI", DBF_DOUBLE, hh); + testdbPutFieldOk("test_ai_rec3.LOW", DBF_DOUBLE, l); + testdbPutFieldOk("test_ai_rec3.LOLO", DBF_DOUBLE, ll); + testdbPutFieldOk("test_ai_rec3.HHSV", DBF_SHORT, menuAlarmSevrMAJOR); + testdbPutFieldOk("test_ai_rec3.HSV", DBF_SHORT, menuAlarmSevrMINOR); + testdbPutFieldOk("test_ai_rec3.LSV", DBF_SHORT, menuAlarmSevrMINOR); + testdbPutFieldOk("test_ai_rec3.LLSV", DBF_SHORT, menuAlarmSevrMAJOR); + + /* test AFTC using a monitor and time stamps */ + testdbPutFieldOk("test_ai_rec3.AFTC", DBF_DOUBLE, aftc); + testdbPutFieldOk("test_ai_rec3.SCAN", DBF_SHORT, menuScan_1_second); + + /* set HIHI alarm VAL */ + testdbPutFieldOk("test_ai_link_rec3.VAL", DBF_DOUBLE, 7550.0); + + /* Create test monitor for alarm SEVR */ + test_mon = testMonitorCreate("test_ai_rec3.VAL", DBE_ALARM, 0); + + /* Get start time */ + epicsTimeGetCurrent(&startTime); + + /* wait for monitor to trigger on the new alarm status*/ + testMonitorWait(test_mon); + epicsTimeGetCurrent(&endTime); + + /* Verify that alarm status is now MAJOR */ + testdbGetFieldEqual("test_ai_rec3.SEVR", DBF_SHORT, menuAlarmSevrMAJOR); + + /* set HI alarm VAL */ + testdbPutFieldOk("test_ai_link_rec3.VAL", DBF_DOUBLE, 5550.0); + + /* Create test monitor for alarm SEVR */ + test_mon = testMonitorCreate("test_ai_rec3.VAL", DBE_ALARM, 0); + + /* Get start time */ + epicsTimeGetCurrent(&startTime); + + /* wait for monitor to trigger on the new alarm status*/ + testMonitorWait(test_mon); + epicsTimeGetCurrent(&endTime); + + /* Verify that alarm status is now MINOR */ + testdbGetFieldEqual("test_ai_rec3.SEVR", DBF_SHORT, menuAlarmSevrMINOR); + + /* Verify that time is at least equal to configured aftc */ + diffTime = epicsTimeDiffInSeconds(&endTime, &startTime); + testOk(diffTime >= aftc, "ATFC time %lf", diffTime); + + // number of tests = 18 +} + +MAIN(aiTest) { + +#ifdef _WIN32 +#if (defined(_MSC_VER) && _MSC_VER < 1900) || \ + (defined(_MINGW) && defined(_TWO_DIGIT_EXPONENT)) + _set_output_format(_TWO_DIGIT_EXPONENT); +#endif +#endif + + testPlan(6+6+11+10+12+14+18+15+6+29+18); + + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("aiTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + test_soft_input(); + test_raw_soft_input(); + test_operator_display(); + test_no_linr_unit_conversion(); + test_slope_linr_unit_conversion(); + test_linear_linr_unit_conversion(); + test_bpt_conversion(); + test_smoothing_filter(); + test_udf(); + test_alarm(); + test_aftc(); + + testIocShutdownOk(); + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/rec/aiTest.db b/modules/database/test/std/rec/aiTest.db new file mode 100644 index 000000000..ef05fb180 --- /dev/null +++ b/modules/database/test/std/rec/aiTest.db @@ -0,0 +1,20 @@ +record(ai, "test_ai_rec") { +} + +record(ao, "test_ai_link_rec") { + +} + +record(ai, "test_ai_rec2") { +} + +record(ao, "test_ai_link_rec2") { + +} + +record(ai, "test_ai_rec3") { +} + +record(ao, "test_ai_link_rec3") { + +} diff --git a/modules/database/test/std/rec/biTest.c b/modules/database/test/std/rec/biTest.c new file mode 100644 index 000000000..8e551e762 --- /dev/null +++ b/modules/database/test/std/rec/biTest.c @@ -0,0 +1,122 @@ +/*************************************************************************\ +* Copyright (c) 2023 Karl Vestin +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "dbUnitTest.h" +#include "testMain.h" +#include "errlog.h" +#include "dbAccess.h" +#include "menuAlarmSevr.h" + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static void test_soft_input(void){ + /* set soft channel */ + testdbPutFieldOk("test_bi_rec.DTYP", DBF_STRING, "Soft Channel"); + testdbPutFieldOk("test_bi_rec.INP", DBF_STRING, "test_bi_link_rec.VAL"); + testdbPutFieldOk("test_bi_link_rec.FLNK", DBF_STRING, "test_bi_rec"); + + /* set VAL to on linked record */ + testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, TRUE); + + /* verify that this record VAL is updated but RVAL is not */ + testdbGetFieldEqual("test_bi_rec.VAL", DBF_SHORT, TRUE); + testdbGetFieldEqual("test_bi_rec.RVAL", DBF_SHORT, FALSE); + + // number of tests = 6 +} + +static void test_raw_soft_input(void){ + /* set soft channel */ + testdbPutFieldOk("test_bi_rec.DTYP", DBF_STRING, "Raw Soft Channel"); + testdbPutFieldOk("test_bi_rec.INP", DBF_STRING, "test_bi_link_rec.VAL"); + testdbPutFieldOk("test_bi_link_rec.FLNK", DBF_STRING, "test_bi_rec"); + + /* set VAL to on linked record */ + testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, TRUE); + + /* verify that this record RVAL and VAL are updated */ + testdbGetFieldEqual("test_bi_rec.VAL", DBF_SHORT, TRUE); + testdbGetFieldEqual("test_bi_rec.RVAL", DBF_SHORT, TRUE); + + // number of tests = 6 +} + +static void test_operator_display(void){ + /* set operator display parameters */ + testdbPutFieldOk("test_bi_rec.ZNAM", DBF_STRING, "ZNAM_TEST"); + testdbPutFieldOk("test_bi_rec.ONAM", DBF_STRING, "ONAM_TEST"); + testdbPutFieldOk("test_bi_rec.DESC", DBF_STRING, "DESC_TEST"); + + /* verify operator display parameters */ + testdbGetFieldEqual("test_bi_rec.ZNAM", DBF_STRING, "ZNAM_TEST"); + testdbGetFieldEqual("test_bi_rec.ONAM", DBF_STRING, "ONAM_TEST"); + testdbGetFieldEqual("test_bi_rec.NAME", DBF_STRING, "test_bi_rec"); + testdbGetFieldEqual("test_bi_rec.DESC", DBF_STRING, "DESC_TEST"); + + /* verify conversion */ + testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, TRUE); + testdbGetFieldEqual("test_bi_rec.VAL", DBF_STRING, "ONAM_TEST"); + testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, FALSE); + testdbGetFieldEqual("test_bi_rec.VAL", DBF_STRING, "ZNAM_TEST"); + + // number of tests = 11 +} + +static void test_alarm(void){ + /* set soft channel */ + testdbPutFieldOk("test_bi_rec.DTYP", DBF_STRING, "Soft Channel"); + testdbPutFieldOk("test_bi_rec.INP", DBF_STRING, "test_bi_link_rec.VAL"); + testdbPutFieldOk("test_bi_link_rec.FLNK", DBF_STRING, "test_bi_rec"); + + /* set start VAL to FALSE*/ + testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, FALSE); + + /* set alarm parameters */ + testdbPutFieldOk("test_bi_rec.ZSV", DBF_SHORT, menuAlarmSevrNO_ALARM); + testdbPutFieldOk("test_bi_rec.OSV", DBF_SHORT, menuAlarmSevrMINOR); + testdbPutFieldOk("test_bi_rec.COSV", DBF_SHORT, menuAlarmSevrINVALID); + + /* verify alarm status is NO_ALARM*/ + testdbGetFieldEqual("test_bi_rec.SEVR", DBF_SHORT, menuAlarmSevrNO_ALARM); + + /* set ZSV to MAJOR and verify that SEVR is now MAJOR */ + testdbPutFieldOk("test_bi_rec.ZSV", DBF_SHORT, menuAlarmSevrMAJOR); + testdbGetFieldEqual("test_bi_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR); + + /* set VAL to 1 on linked record and verify that COSV now sets the SEVR to INVALID */ + testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, TRUE); + testdbGetFieldEqual("test_bi_rec.SEVR", DBF_SHORT, menuAlarmSevrINVALID); + + /* verify LAML */ + testdbGetFieldEqual("test_bi_rec.LALM", DBF_SHORT, TRUE); + + // number of tests = 13 +} + +MAIN(biTest) { + + testPlan(6+6+11+13); + + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("biTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + test_soft_input(); + test_raw_soft_input(); + test_operator_display(); + test_alarm(); + + testIocShutdownOk(); + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/rec/biTest.db b/modules/database/test/std/rec/biTest.db new file mode 100644 index 000000000..4304cce91 --- /dev/null +++ b/modules/database/test/std/rec/biTest.db @@ -0,0 +1,6 @@ +record(bi, "test_bi_rec") { +} + +record(bo, "test_bi_link_rec") { + +} diff --git a/modules/database/test/std/rec/boTest.c b/modules/database/test/std/rec/boTest.c new file mode 100644 index 000000000..55865c79c --- /dev/null +++ b/modules/database/test/std/rec/boTest.c @@ -0,0 +1,158 @@ +/*************************************************************************\ +* Copyright (c) 2023 Karl Vestin +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "dbUnitTest.h" +#include "testMain.h" +#include "errlog.h" +#include "dbAccess.h" +#include "menuAlarmSevr.h" +#include "menuIvoa.h" + +#include "boRecord.h" + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static void test_soft_output(void){ + /* set soft channel */ + testdbPutFieldOk("test_bo_rec.DTYP", DBF_STRING, "Soft Channel"); + testdbPutFieldOk("test_bo_rec.OUT", DBF_STRING, "test_bo_link_rec"); + + /* set VAL to process record */ + testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE); + + /* verify that OUT record is updated */ + testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, TRUE); + + // number of tests = 4 +} + +static void test_high(void){ + const int high_time = 2; + testMonitor* test_mon = NULL; + epicsTimeStamp startTime; + epicsTimeStamp endTime; + double diffTime = 0; + double diffTimeTolerance = 0.1; + + /* set soft channel */ + testdbPutFieldOk("test_bo_rec.DTYP", DBF_STRING, "Soft Channel"); + testdbPutFieldOk("test_bo_rec.OUT", DBF_STRING, "test_bo_link_rec"); + + /* set HIGH to 2 seconds */ + testdbPutFieldOk("test_bo_rec.HIGH", DBF_SHORT, high_time); + + /* Create test monitor */ + test_mon = testMonitorCreate("test_bo_rec.VAL", DBR_SHORT, 0); + + /* Get start time */ + epicsTimeGetCurrent(&startTime); + + /* set VAL to process record */ + testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE); + + /* wait and verfiy time */ + testMonitorWait(test_mon); + epicsTimeGetCurrent(&endTime); + + /* EPICS timers have a tendency to trip slightly early, hence the test tolerance is added here to avoid false positives in testing */ + diffTime = epicsTimeDiffInSeconds(&endTime, &startTime) + diffTimeTolerance; + testOk(diffTime >= high_time, "HIGH time %lf", diffTime); + + /* verify that both records are set back to 0 */ + testdbGetFieldEqual("test_bo_rec.VAL", DBF_SHORT, FALSE); + testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, FALSE); + + testMonitorDestroy(test_mon); + // number of tests = 7 +} + +static void test_operator_display(void){ + /* set operator display parameters */ + testdbPutFieldOk("test_bo_rec.ZNAM", DBF_STRING, "ZNAM_TEST"); + testdbPutFieldOk("test_bo_rec.ONAM", DBF_STRING, "ONAM_TEST"); + testdbPutFieldOk("test_bo_rec.DESC", DBF_STRING, "DESC_TEST"); + + /* verify operator display parameters */ + testdbGetFieldEqual("test_bo_rec.ZNAM", DBF_STRING, "ZNAM_TEST"); + testdbGetFieldEqual("test_bo_rec.ONAM", DBF_STRING, "ONAM_TEST"); + testdbGetFieldEqual("test_bo_rec.NAME", DBF_STRING, "test_bo_rec"); + testdbGetFieldEqual("test_bo_rec.DESC", DBF_STRING, "DESC_TEST"); + + /* verify conversion */ + testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE); + testdbGetFieldEqual("test_bo_rec.VAL", DBF_STRING, "ONAM_TEST"); + testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, FALSE); + testdbGetFieldEqual("test_bo_rec.VAL", DBF_STRING, "ZNAM_TEST"); + + // number of tests = 11 +} + +static void test_alarm(void){ + /* set soft channel */ + testdbPutFieldOk("test_bo_rec.DTYP", DBF_STRING, "Soft Channel"); + testdbPutFieldOk("test_bo_rec.OUT", DBF_STRING, "test_bo_link_rec"); + + /* Set start VAL to FALSE*/ + testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, FALSE); + + /* set alarm parameters */ + testdbPutFieldOk("test_bo_rec.ZSV", DBF_SHORT, menuAlarmSevrNO_ALARM); + testdbPutFieldOk("test_bo_rec.OSV", DBF_SHORT, menuAlarmSevrMINOR); + testdbPutFieldOk("test_bo_rec.COSV", DBF_SHORT, menuAlarmSevrINVALID); + testdbPutFieldOk("test_bo_rec.IVOA", DBF_SHORT, menuIvoaSet_output_to_IVOV); + testdbPutFieldOk("test_bo_rec.IVOV", DBF_SHORT, FALSE); + + /* Verify alarm status is NO_ALARM*/ + testdbGetFieldEqual("test_bo_rec.SEVR", DBF_SHORT, menuAlarmSevrNO_ALARM); + + /* Set ZSV to MAJOR and verify that SEVR is now MAJOR */ + testdbPutFieldOk("test_bo_rec.ZSV", DBF_SHORT, menuAlarmSevrMAJOR); + testdbGetFieldEqual("test_bo_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR); + + /* Set VAL to 1 and verify that COSV now sets the SEVR to INVALID and in turn triggers the IVOV on output */ + testdbPutFieldOk("test_bo_link_rec.VAL", DBF_SHORT, TRUE); + testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE); + testdbGetFieldEqual("test_bo_rec.SEVR", DBF_SHORT, menuAlarmSevrINVALID); + testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, FALSE); + + /* verify IVOV continue normally action */ + testdbPutFieldOk("test_bo_rec.IVOA", DBF_SHORT, menuIvoaContinue_normally); + testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE); + testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, TRUE); + + /* verify IVOV dont drive outputs action */ + testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, FALSE); + testdbPutFieldOk("test_bo_rec.IVOA", DBF_SHORT, menuIvoaDon_t_drive_outputs); + testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE); + testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, FALSE); + + // number of tests = 22 +} + +MAIN(boTest) { + + testPlan(4+7+11+22); + + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("boTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + test_soft_output(); + test_high(); + test_operator_display(); + test_alarm(); + + testIocShutdownOk(); + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/rec/boTest.db b/modules/database/test/std/rec/boTest.db new file mode 100644 index 000000000..0e39cf5f0 --- /dev/null +++ b/modules/database/test/std/rec/boTest.db @@ -0,0 +1,6 @@ +record(bo, "test_bo_rec") { +} + +record(bi, "test_bo_link_rec") { + +} diff --git a/modules/database/test/std/rec/epicsRunRecordTests.c b/modules/database/test/std/rec/epicsRunRecordTests.c index 79db80e3f..60ddd0f32 100644 --- a/modules/database/test/std/rec/epicsRunRecordTests.c +++ b/modules/database/test/std/rec/epicsRunRecordTests.c @@ -24,6 +24,10 @@ int asyncSoftTest(void); int simmTest(void); int mbbioDirectTest(void); int scanEventTest(void); +int boTest(void); +int biTest(void); +int printfTest(void); +int aiTest(void); void epicsRunRecordTests(void) { @@ -51,5 +55,13 @@ void epicsRunRecordTests(void) runTest(scanEventTest); + runTest(boTest); + + runTest(biTest); + + runTest(printfTest); + + runTest(aiTest); + epicsExit(0); /* Trigger test harness */ } diff --git a/modules/database/test/std/rec/printfTest.c b/modules/database/test/std/rec/printfTest.c new file mode 100644 index 000000000..b62b1d9c0 --- /dev/null +++ b/modules/database/test/std/rec/printfTest.c @@ -0,0 +1,577 @@ +/*************************************************************************\ +* Copyright (c) 2023 Karl Vestin +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "dbUnitTest.h" +#include "testMain.h" +#include "errlog.h" +#include "dbAccess.h" + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static void test_double_percentile(void){ + const char format_string[] = "Format test string %%d"; + const char result_string[] = "Format test string %d"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 1); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_d_format(void){ + const char format_string[] = "Format test string %d"; + const char result_string[] = "Format test string 7"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 7); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_c_format(void){ + const char format_string[] = "Format test string %c"; + const char result_string[] = "Format test string R"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0, 82 is ASCII for R */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 82); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_i_format(void){ + const char format_string[] = "Format test string %i"; + const char result_string[] = "Format test string -27"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, -27); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_o_format(void){ + const char format_string[] = "Format test string %o"; + const char result_string[] = "Format test string 6777"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 06777); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_u_format(void){ + const char format_string[] = "Format test string %u"; + const char result_string[] = "Format test string 8009"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 8009); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_x_format(void){ + const char format_string[] = "Format test string %x"; + const char result_string[] = "Format test string fafa"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 0xfafa); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_X_format(void){ + const char format_string[] = "Format test string %X"; + const char result_string[] = "Format test string BA"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 0x00ba); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_e_format(void){ + const char format_string[] = "Format test string %e"; + const char result_string[] = "Format test string -1.400000e+01"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, -14); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_E_format(void){ + const char format_string[] = "Format test string %E"; + const char result_string[] = "Format test string 1.992000E+03"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 1992); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_f_format(void){ + const char format_string[] = "Format test string %f"; + const char result_string[] = "Format test string -0.062000"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_DOUBLE, -0.062); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_F_format(void){ + const char format_string[] = "Format test string %F"; + const char result_string[] = "Format test string 6729982.999000"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_DOUBLE, 6729982.999); + + /* verify that string is formatted as expected */ + // visual studio less than 2015 does not support %F + // mingw/gcc also fails, suspect this may be gcc version + // related and checking __GNUC__ could resolve but + // initial attempts didn't work so excluding mingw entirely for now + #ifdef _WIN32 + #if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(_MINGW) + testTodoBegin("Fails on windows with old visual studio versions and mingw"); + #endif + #endif + + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + #ifdef _WIN32 + #if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(_MINGW) + testTodoEnd(); + #endif + #endif + // number of tests = 3 +} + +static void test_g_format(void){ + const char format_string[] = "Format test string %g"; + const char result_string[] = "Format test string -0.093"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_DOUBLE, -93e-3); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_G_format(void){ + const char format_string[] = "Format test string %G"; + const char result_string[] = "Format test string 7.2884E+08"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 728839938); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_s_format(void){ + + const char format_string[] = "Format test string %d %s"; + const char result_string[] = "Format test string 7 Molly III"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 7); + + /* set value on inp1 */ + testdbPutFieldOk("test_printf_inp1_rec.VAL", DBF_STRING, "Molly III"); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 4 +} + +static void test_plus_flag(void){ + + const char format_string[] = "Format test string %+d"; + const char result_string[] = "Format test string +7"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 7); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_minus_flag(void){ + + const char format_string[] = "Format test string %-10d"; + const char result_string[] = "Format test string 18 "; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 18); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_space_flag(void){ + + const char format_string[] = "Format test string % d"; + const char result_string[] = "Format test string 12"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 12); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_hash_flag(void){ + + const char format_string[] = "Format test string %#o"; + const char result_string[] = "Format test string 014"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 014); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_min_width_flag(void){ + + const char format_string[] = "Format test string %04i"; + const char result_string[] = "Format test string 0003"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 3); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_prec_flag(void){ + + const char format_string[] = "Format test string %.4f"; + const char result_string[] = "Format test string 71.2000"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_DOUBLE, 71.2); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_h_flag(void){ + + const char format_string[] = "Format test string %hx"; + const char result_string[] = "Format test string baba"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 0xffbaba); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 3 +} + +static void test_hh_flag(void){ + + const char format_string[] = "Format test string %hhx"; + const char result_string[] = "Format test string c1"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 0xffc0c1); + + /* verify that string is formatted as expected */ + #ifdef __rtems__ + testTodoBegin("Fails on UB-20 gcc-9 on RTEMS"); + #endif + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + #ifdef __rtems__ + testTodoEnd(); + #endif + + // number of tests = 3 +} + +static void test_l_flag(void){ + + const char format_string[] = "Format test string %lx"; + const char result_string[] = "Format test string 70a1c0c1"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 0x70a1c0c1); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + // number of tests = 3 +} + +static void test_ll_flag(void){ + + const char format_string[] = "%d %s %llx"; + const char result_string[] = "2 Reperbahn ba0110baa0a1c0c1"; + const epicsInt64 value = 0xba0110baa0a1c0c1ull; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 2); + + /* set value on inp1 */ + testdbPutFieldOk("test_printf_inp1_rec.VAL", DBF_STRING, "Reperbahn"); + + /* set value on inp2 */ + testdbPutFieldOk("test_printf_inp2_rec.VAL", DBR_INT64, value); + testdbGetFieldEqual("test_printf_inp2_rec.VAL", DBR_INT64, value); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 6 +} + +static void test_sizv(void){ + + const char format_string[] = "%d %s %llx"; + const char result_string[] = "99 123456789012345678901234567890 6640baa0a1"; + const epicsInt64 value = 0x06640baa0a1c0c1ull; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 99); + + /* set value on inp1 */ + testdbPutFieldOk("test_printf_inp1_rec.VAL", DBF_STRING, "123456789012345678901234567890"); + + /* set value on inp2 */ + testdbPutFieldOk("test_printf_inp2_rec.VAL", DBR_INT64, value); + testdbGetFieldEqual("test_printf_inp2_rec.VAL", DBR_INT64, value); + + /* verify that string is formatted as expected */ + testdbGetArrFieldEqual("test_printf_rec.VAL$", DBF_CHAR, 45, 45, result_string); + + // number of tests = 6 +} + +static void test_all_inputs(void){ + + const char format_string[] = "%d %s %i %i %i %i %i %i %i %i"; + const char result_string[] = "0 One 2 3 4 5 6 7 8 9"; + + /* set format string */ + testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string); + + /* set value on inp0 */ + testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 0); + + /* set value on inp1 */ + testdbPutFieldOk("test_printf_inp1_rec.VAL", DBF_STRING, "One"); + + /* set value on inp2 */ + testdbPutFieldOk("test_printf_inp2_rec.VAL", DBF_SHORT, 2); + + /* set value on inp3 */ + testdbPutFieldOk("test_printf_inp3_rec.VAL", DBF_SHORT, 3); + + /* set value on inp4 */ + testdbPutFieldOk("test_printf_inp4_rec.VAL", DBF_SHORT, 4); + + /* set value on inp5 */ + testdbPutFieldOk("test_printf_inp5_rec.VAL", DBF_SHORT, 5); + + /* set value on inp6 */ + testdbPutFieldOk("test_printf_inp6_rec.VAL", DBF_SHORT, 6); + + /* set value on inp7 */ + testdbPutFieldOk("test_printf_inp7_rec.VAL", DBF_SHORT, 7); + + /* set value on inp8 */ + testdbPutFieldOk("test_printf_inp8_rec.VAL", DBF_SHORT, 8); + + /* set value on inp9 */ + testdbPutFieldOk("test_printf_inp9_rec.VAL", DBF_SHORT, 9); + + /* verify that string is formatted as expected */ + testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string); + + // number of tests = 12 +} + +MAIN(printfTest) { +#ifdef _WIN32 +#if (defined(_MSC_VER) && _MSC_VER < 1900) || \ + (defined(_MINGW) && defined(_TWO_DIGIT_EXPONENT)) + _set_output_format(_TWO_DIGIT_EXPONENT); +#endif +#endif + + testPlan(3+3+3+3+3+3+3+3+4+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+6+6+12); + + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("printfTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + test_double_percentile(); + test_c_format(); + test_d_format(); + test_i_format(); + test_o_format(); + test_u_format(); + test_x_format(); + test_X_format(); + test_e_format(); + test_E_format(); + test_f_format(); + test_F_format(); + test_g_format(); + test_G_format(); + test_s_format(); + test_plus_flag(); + test_minus_flag(); + test_space_flag(); + test_hash_flag(); + test_min_width_flag(); + test_prec_flag(); + test_h_flag(); + test_hh_flag(); + test_l_flag(); + test_ll_flag(); + test_sizv(); + test_all_inputs(); + + testIocShutdownOk(); + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/rec/printfTest.db b/modules/database/test/std/rec/printfTest.db new file mode 100644 index 000000000..1f4de2e6e --- /dev/null +++ b/modules/database/test/std/rec/printfTest.db @@ -0,0 +1,53 @@ +record(printf, "test_printf_rec") { + field(SIZV, 45) + field(INP0, "test_printf_inp0_rec") + field(INP1, "test_printf_inp1_rec") + field(INP2, "test_printf_inp2_rec") + field(INP3, "test_printf_inp3_rec") + field(INP4, "test_printf_inp4_rec") + field(INP5, "test_printf_inp5_rec") + field(INP6, "test_printf_inp6_rec") + field(INP7, "test_printf_inp7_rec") + field(INP8, "test_printf_inp8_rec") + field(INP9, "test_printf_inp9_rec") +} + +record(ai, "test_printf_inp0_rec") { + field(FLNK, "test_printf_rec") +} + +record(stringin, "test_printf_inp1_rec") { + field(FLNK, "test_printf_rec") +} + +record(int64in, "test_printf_inp2_rec") { + field(FLNK, "test_printf_rec") +} + +record(ai, "test_printf_inp3_rec") { + field(FLNK, "test_printf_rec") +} + +record(ai, "test_printf_inp4_rec") { + field(FLNK, "test_printf_rec") +} + +record(ai, "test_printf_inp5_rec") { + field(FLNK, "test_printf_rec") +} + +record(ai, "test_printf_inp6_rec") { + field(FLNK, "test_printf_rec") +} + +record(ai, "test_printf_inp7_rec") { + field(FLNK, "test_printf_rec") +} + +record(ai, "test_printf_inp8_rec") { + field(FLNK, "test_printf_rec") +} + +record(ai, "test_printf_inp9_rec") { + field(FLNK, "test_printf_rec") +} diff --git a/modules/libcom/src/log/iocLogServer.c b/modules/libcom/src/log/iocLogServer.c index 809052bf4..674175aac 100644 --- a/modules/libcom/src/log/iocLogServer.c +++ b/modules/libcom/src/log/iocLogServer.c @@ -113,8 +113,8 @@ int main(void) pserver->pfdctx = (void *) fdmgr_init(); if (!pserver->pfdctx) { - free(pserver); fprintf(stderr, "iocLogServer: %s\n", strerror(errno)); + free(pserver); return IOCLS_ERROR; } @@ -149,6 +149,7 @@ int main(void) fprintf (stderr, "iocLogServer: a server is already installed on port %u?\n", (unsigned)ioc_log_port); + free(pserver); return IOCLS_ERROR; } @@ -158,6 +159,7 @@ int main(void) char sockErrBuf[64]; epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) ); fprintf(stderr, "iocLogServer: listen err %s\n", sockErrBuf); + free(pserver); return IOCLS_ERROR; } @@ -174,6 +176,7 @@ int main(void) char sockErrBuf[64]; epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) ); fprintf(stderr, "iocLogServer: ioctl FIONBIO err %s\n", sockErrBuf); + free(pserver); return IOCLS_ERROR; } @@ -190,6 +193,7 @@ int main(void) "File access problems to `%s' because `%s'\n", ioc_log_file_name, strerror(errno)); + free(pserver); return IOCLS_ERROR; } @@ -202,6 +206,7 @@ int main(void) if (status < 0) { fprintf(stderr, "iocLogServer: failed to add read callback\n"); + free(pserver); return IOCLS_ERROR; } diff --git a/modules/libcom/src/macLib/macEnv.c b/modules/libcom/src/macLib/macEnv.c index dc8eed2ba..2eeb033ce 100644 --- a/modules/libcom/src/macLib/macEnv.c +++ b/modules/libcom/src/macLib/macEnv.c @@ -30,6 +30,7 @@ macDefExpand(const char *str, MAC_HANDLE *macros) static const char * pairs[] = { "", "environ", NULL, NULL }; long destCapacity = 128; char *dest = NULL; + char *newdest = NULL; int n; if (macros) { @@ -61,8 +62,11 @@ macDefExpand(const char *str, MAC_HANDLE *macros) } else { size_t unused = destCapacity - ++n; - if (unused >= 20) - dest = realloc(dest, n); + if (unused >= 20) { + newdest = realloc(dest, n); + if (newdest) + dest = newdest; + } } done: diff --git a/modules/libcom/src/misc/epicsStdlib.h b/modules/libcom/src/misc/epicsStdlib.h index 660ad9abc..dcb9fb584 100644 --- a/modules/libcom/src/misc/epicsStdlib.h +++ b/modules/libcom/src/misc/epicsStdlib.h @@ -87,8 +87,8 @@ LIBCOM_API int * \brief Convert a string to a double type * * \param str Pointer to a constant character array - * \param to Pointer to the specified type (this will be set during the conversion) - * \param units Pointer to a char * (this will be set with the units string) + * \param to Pointer to the specified type (this will be set only upon successful conversion) + * \param units Pointer to a char * (this will be set with the units string only upon successful conversion) * \return Status code (0=OK, see macro definitions for possible errors) */ LIBCOM_API int diff --git a/modules/libcom/src/misc/epicsString.c b/modules/libcom/src/misc/epicsString.c index ca61d9b8c..bcd10a504 100644 --- a/modules/libcom/src/misc/epicsString.c +++ b/modules/libcom/src/misc/epicsString.c @@ -231,27 +231,39 @@ int epicsStrPrintEscaped(FILE *fp, const char *s, size_t len) { int nout = 0; + if (fp == NULL) + return -1; + + if (s == NULL || strlen(s) == 0 || len == 0) + return 0; // No work to do + while (len--) { char c = *s++; + int rc = 0; switch (c) { - case '\a': nout += fprintf(fp, "\\a"); break; - case '\b': nout += fprintf(fp, "\\b"); break; - case '\f': nout += fprintf(fp, "\\f"); break; - case '\n': nout += fprintf(fp, "\\n"); break; - case '\r': nout += fprintf(fp, "\\r"); break; - case '\t': nout += fprintf(fp, "\\t"); break; - case '\v': nout += fprintf(fp, "\\v"); break; - case '\\': nout += fprintf(fp, "\\\\"); break; - case '\'': nout += fprintf(fp, "\\'"); break; - case '\"': nout += fprintf(fp, "\\\""); break; + case '\a': rc = fprintf(fp, "\\a"); break; + case '\b': rc = fprintf(fp, "\\b"); break; + case '\f': rc = fprintf(fp, "\\f"); break; + case '\n': rc = fprintf(fp, "\\n"); break; + case '\r': rc = fprintf(fp, "\\r"); break; + case '\t': rc = fprintf(fp, "\\t"); break; + case '\v': rc = fprintf(fp, "\\v"); break; + case '\\': rc = fprintf(fp, "\\\\"); break; + case '\'': rc = fprintf(fp, "\\'"); break; + case '\"': rc = fprintf(fp, "\\\""); break; default: if (isprint(0xff & (int)c)) - nout += fprintf(fp, "%c", c); + rc = fprintf(fp, "%c", c); else - nout += fprintf(fp, "\\x%02x", (unsigned char)c); + rc = fprintf(fp, "\\x%02x", (unsigned char)c); break; } + if (rc < 0) { + return rc; + } else { + nout += rc; + } } return nout; } diff --git a/modules/libcom/src/osi/os/RTEMS-posix/osdMessageQueue.c b/modules/libcom/src/osi/os/RTEMS-posix/osdMessageQueue.c index 7c7ea8aad..7ccdc878d 100644 --- a/modules/libcom/src/osi/os/RTEMS-posix/osdMessageQueue.c +++ b/modules/libcom/src/osi/os/RTEMS-posix/osdMessageQueue.c @@ -41,12 +41,17 @@ epicsMessageQueueCreate(unsigned int capacity, unsigned int maximumMessageSize) { struct mq_attr the_attr; epicsMessageQueueId id = (epicsMessageQueueId)calloc(1, sizeof(*id)); + if (!id) { + fprintf (stderr, "Can't allocate message queue: %s\n", strerror(errno)); + return NULL; + } sprintf(id->name, "MQ_%0d", epicsAtomicIncrIntT(&idCnt)); the_attr.mq_maxmsg = capacity; the_attr.mq_msgsize = maximumMessageSize; id->id = mq_open(id->name, O_RDWR | O_CREAT | O_EXCL, 0644, &the_attr); if (id->id <0) { fprintf (stderr, "Can't create message queue: %s\n", strerror (errno)); + free(id); return NULL; } return id; diff --git a/modules/libcom/test/epicsStringTest.c b/modules/libcom/test/epicsStringTest.c index 33110434a..5221eac8b 100644 --- a/modules/libcom/test/epicsStringTest.c +++ b/modules/libcom/test/epicsStringTest.c @@ -195,6 +195,42 @@ void testStrTok(void) testTok(NULL, " \t", "bbb ", "bbb", ""); } +static +void testEpicsStrPrintEscaped(void) +{ + const char *filename = "testEpicsStrPrintEscaped"; + // Avoid printing to stdout by redirecting everything to a file + FILE *testFile = fopen(filename, "a"); + FILE *readOnly = fopen(filename, "r"); + + testDiag("testEpicsStrPrintEscaped()"); + + // Passing cases + testOk1(epicsStrPrintEscaped(testFile, "1234", 4) == 4); + testOk1(epicsStrPrintEscaped(testFile, "\a\b\f\n\r\t\v\\\'\"", 10) == 20); + + // Failing cases + testOk1(epicsStrPrintEscaped(NULL, "1234", 4) == -1); + // On some platforms (specifially certain versions of MinGW-w64), fprintf + // is broken and does not return -1 when the write operation fails. On + // those platforms, epicsStrPrintEscaped cannot detect failure either, so + // testing that it reports failure does not make sense on those platforms. + // For this reason, we only test this behavior of epcisStrPrintEscaped when + // after checking that fprintf behaves correctly. + if (fprintf(readOnly, "test") == -1) { + testOk1(epicsStrPrintEscaped(readOnly, "1234", 4) == -1); + } else { + testSkip(1, "fprintf is broken on this system"); + } + testOk1(epicsStrPrintEscaped(testFile, NULL, 4) == 0); + testOk1(epicsStrPrintEscaped(testFile, "", 4) == 0); + testOk1(epicsStrPrintEscaped(testFile, "1234", 0) == 0); + + fclose(testFile); + fclose(readOnly); + remove(filename); +} + MAIN(epicsStringTest) { const char * const empty = ""; @@ -209,7 +245,7 @@ MAIN(epicsStringTest) char *s; int status; - testPlan(439); + testPlan(446); testChars(); @@ -407,6 +443,7 @@ MAIN(epicsStringTest) testDistance(); testStrTok(); + testEpicsStrPrintEscaped(); return testDone(); }