/*************************************************************************\ * Copyright (c) 2017 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. \*************************************************************************/ #include #include "dbAccess.h" #include "dbStaticLib.h" #include "dbTest.h" #include "dbUnitTest.h" #include "epicsThread.h" #include "epicsEvent.h" #include "errlog.h" #include "registryFunction.h" #include "subRecord.h" #include "testMain.h" static void checkDtyp(const char *rec) { char dtyp[16]; strcpy(dtyp, rec); strcat(dtyp, ".DTYP"); testdbGetFieldEqual(dtyp, DBR_LONG, 0); testdbGetFieldEqual(dtyp, DBR_STRING, "Soft Channel"); } static void doProcess(const char *rec) { char proc[16]; strcpy(proc, rec); strcat(proc, ".PROC"); testdbPutFieldOk(proc, DBR_CHAR, 1); } /* Group 0 are soft-channel input records with INP being a DB or CA link * to the PV 'source'. Their VAL fields all start out with the default * value for the type, i.e. 0 or an empty string. Triggering record * processing reads the value from the 'source' PV. */ static void testGroup0(void) { const char ** rec; const char * records[] = { "ai0", "bi0", "di0", "ii0", "li0", "lsi0", "mi0", "si0", "ai0c", "bi0c", "di0c", "ii0c", "li0c", "lsi0c", "mi0c", "si0c", NULL }; testDiag("============ Starting %s ============", EPICS_FUNCTION); testdbPutFieldOk("source", DBR_LONG, 1); /* The above put sends CA monitors to all of the CA links, but * doesn't trigger record processing (the links are not CP/CPP). * How could we wait until all of those monitors have arrived, * instead of just waiting for an arbitrary time period? */ epicsThreadSleep(1.0); /* FIXME: Wait here? */ for (rec = records; *rec; rec++) { if (strncmp(*rec, "lsi0", 4) != 0) testdbGetFieldEqual(*rec, DBR_LONG, 0); checkDtyp(*rec); doProcess(*rec); testdbGetFieldEqual(*rec, DBR_LONG, 1); } testdbPutFieldOk("source", DBR_LONG, 0); epicsThreadSleep(1.0); /* FIXME: Wait here as above */ for (rec = records; *rec; rec++) { doProcess(*rec); testdbGetFieldEqual(*rec, DBR_LONG, 0); } } /* Group 1 are all soft-channel input records with INP being a non-zero * "const" JSON-link, 9 for most records, 1 for the binary. Their VAL * fields should all be initialized to that constant value. Triggering * record processing should succeed, but shouldn't change VAL. */ static void testGroup1(void) { const char ** rec; const char * records[] = { "bi1", "ai1", "di1", "ii1", "li1", "lsi1", "mi1", "si1", NULL }; int init = 1; /* bi1 initializes to 1 */ testDiag("============ Starting %s ============", EPICS_FUNCTION); for (rec = records; *rec; rec++) { testdbGetFieldEqual(*rec, DBR_LONG, init); doProcess(*rec); testdbGetFieldEqual(*rec, DBR_LONG, init); init = 9; /* other records initialize to 9 */ } } /* Group 2 are all soft-channel input records with INP being a CONSTANT * link with value 9 for most records, 1 for the binary. Their VAL * fields should all be initialized to that constant value. Triggering * record processing should succeed, but shouldn't change VAL. */ static void testGroup2(void) { const char ** rec; const char * records[] = { "bi2", "ai2", "di2", "ii2", "li2", "mi2", NULL }; int init = 1; /* bi1 initializes to 1 */ testDiag("============ Starting %s ============", EPICS_FUNCTION); for (rec = records; *rec; rec++) { testdbGetFieldEqual(*rec, DBR_LONG, init); doProcess(*rec); testdbGetFieldEqual(*rec, DBR_LONG, init); init = 9; /* other records initialize to 9 */ } } int dest; epicsEventId destEvent; static long destSubr(subRecord *prec) { dest = prec->val; prec->val = -1; epicsEventMustTrigger(destEvent); return 0; } static void checkOutput3(const char *rec, int value) { testDiag("Checking record '%s'", rec); testdbPutFieldOk(rec, DBR_LONG, value); epicsEventMustWait(destEvent); testOk(dest == value, "value %d output -> %d", value, dest); } /* Group 3 are all soft-channel output records with OUT being a DB or * local CA link to the subRecord 'dest'; DB links have the PP flag, * for CA links the VAL field is marked PP. Putting a value to the * output record writes that value to 'dest'. */ static void testGroup3(void) { const char ** rec; const char * records[] = { "ao3", "bo3", "io3", "lo3", "lso3", "mo3", "so3", "ao3c", "bo3c", "io3c", "lo3c", "lso3c", "mo3c", "so3c", NULL, }; destEvent = epicsEventMustCreate(epicsEventEmpty); testDiag("============ Starting %s ============", EPICS_FUNCTION); for (rec = records; *rec; rec++) { checkOutput3(*rec, 1); checkDtyp(*rec); } checkOutput3("do3.B0", 1); checkDtyp("do3"); checkOutput3("do3c.B0", 1); checkDtyp("do3c"); for (rec = records; *rec; rec++) { checkOutput3(*rec, 0); } checkOutput3("do3.B0", 0); checkOutput3("do3c.B0", 0); } static void checkOutput4(const char *rec, int value) { testDiag("Checking record '%s'", rec); testdbPutFieldOk(rec, DBR_LONG, value); } /* Group 4 are all soft-channel output records with OUT being empty * (i.e. a CONSTANT link). Putting a value to the record must succeed. */ static void testGroup4(void) { const char ** rec; const char * records[] = { "ao4", "bo4", "do4.B0", "io4", "lo4", "lso4", "mo4", "so4", NULL, }; testDiag("============ Starting %s ============", EPICS_FUNCTION); for (rec = records; *rec; rec++) { checkOutput4(*rec, 0); } } void recTestIoc_registerRecordDeviceDriver(struct dbBase *); MAIN(softTest) { testPlan(258); testdbPrepare(); testdbReadDatabase("recTestIoc.dbd", NULL, NULL); recTestIoc_registerRecordDeviceDriver(pdbbase); registryFunctionAdd("destSubr", (REGISTRYFUNCTION) destSubr); testdbReadDatabase("softTest.db", NULL, NULL); eltc(0); testIocInitOk(); eltc(1); testGroup0(); testGroup1(); testGroup2(); testGroup3(); testGroup4(); testIocShutdownOk(); testdbCleanup(); return testDone(); }