/*************************************************************************\ * Copyright (c) 2014 Brookhaven Science Assoc. as operator of Brookhaven * 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 */ #include "string.h" #include "epicsString.h" #include "dbUnitTest.h" #include "epicsThread.h" #include "iocInit.h" #include "dbBase.h" #include "dbDefs.h" #include "link.h" #include "dbAccess.h" #include "registry.h" #include "dbStaticLib.h" #include "dbStaticPvt.h" #include "osiFileName.h" #include "dbmf.h" #include "errlog.h" #include #include "xRecord.h" #include "jlinkz.h" #include "testMain.h" static int testStrcmp(int expect, const char *A, const char *B) { static const char op[] = "<=>"; int ret = strcmp(A,B); testOk(ret==expect, "\"%s\" %c= \"%s\"", A, op[expect+1], B); return ret==expect; } void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); #define TEST_CONSTANT(SET, EXPECT) {SET, {CONSTANT, EXPECT}} #define TEST_PV_LINK(SET, PV, MOD) {SET, {PV_LINK, PV, MOD}} static const struct testParseDataT { const char * const str; dbLinkInfo info; } testParseData[] = { TEST_CONSTANT("", ""), TEST_CONSTANT("0.1", "0.1"), TEST_CONSTANT(" 0.2\t ", "0.2"), TEST_PV_LINK("0.1a", "0.1a", 0), TEST_PV_LINK(" hello ", "hello", 0), TEST_PV_LINK(" hellox MSI", "hellox", pvlOptMSI), TEST_PV_LINK(" world MSICP", "world", pvlOptMSI|pvlOptCP), {"#C14 S145 @testing", {VME_IO, "testing", 0, "CS", {14, 145}}}, {"#C14 S0x1b @testing", {VME_IO, "testing", 0, "CS", {14, 0x1b}}}, {"#C14 S145", {VME_IO, "", 0, "CS", {14, 145}}}, {"#B11 C12 N13 A14 F15 @cparam", {CAMAC_IO, "cparam", 0, "BCNAF", {11, 12, 13, 14, 15}}}, {"#B11 C12 N13 A14 F15", {CAMAC_IO, "", 0, "BCNAF", {11, 12, 13, 14, 15}}}, {" #B111 C112 N113 @cparam", {CAMAC_IO, "cparam", 0, "BCN", {111, 112, 113}}}, {" @hello world ", {INST_IO, "hello world", 0, "", /*{}*/}}, {" {\"x\":true} ", {JSON_LINK, "{\"x\":true}", 0, "", /*{}*/}}, {" {'x':true} ", {JSON_LINK, "{'x':true}", 0, "", /*{}*/}}, {NULL} }; static void testLinkParse(void) { const struct testParseDataT *td = testParseData; dbLinkInfo info; testDiag("\n# Checking link parsing\n#"); testdbPrepare(); testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); dbTestIoc_registerRecordDeviceDriver(pdbbase); testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); eltc(0); testIocInitOk(); eltc(1); for (;td->str; td++) { int i, N; testDiag("Parsing \"%s\"", td->str); testOk(dbParseLink(td->str, DBF_INLINK, &info, "dummy","INP") == 0, "Parser returned OK"); if (!testOk(info.ltype == td->info.ltype, "Link type value")) testDiag("Expected %d, got %d", td->info.ltype, info.ltype); if (td->info.target && info.target) testStrcmp(0, info.target, td->info.target); else if(!!td->info.target ^ !!info.target) testFail("info target NULL mismatch %s %s", info.target, td->info.target); else testPass("info target NULL as expected"); if (info.ltype == td->info.ltype) { switch (info.ltype) { case PV_LINK: if (!testOk(info.modifiers == td->info.modifiers, "PV Link modifier flags")) testDiag("Expected %d, got %d", td->info.modifiers, info.modifiers); break; case VME_IO: case CAMAC_IO: testStrcmp(0, info.hwid, td->info.hwid); N = strlen(td->info.hwid); for (i=0; iinfo.hwnums[i], "%d == %d", info.hwnums[i], td->info.hwnums[i]); } } dbFreeLinkInfo(&info); } info.modifiers |= pvlOptCPP; dbParseLink("something CPP", DBF_OUTLINK, &info, "dummy","OUT"); testOk(info.modifiers == 0, "CPP modifier was discarded"); dbFreeLinkInfo(&info); info.modifiers |= pvlOptCP; dbParseLink("something CP", DBF_OUTLINK, &info, "dummy","OUT"); testOk(info.modifiers == 0, "CP modifier was discarded"); dbFreeLinkInfo(&info); testIocShutdownOk(); testdbCleanup(); } static const char *testParseFailData[] = { "#", "#S", "#ABC", "#A0 B", "#A0 B @", "#A0 B C @", "#R1 M2 D3 E4 @oops", /* RF_IO has no parm */ NULL }; static void testLinkFailParse(void) { const char * const *td = testParseFailData; dbLinkInfo info; testDiag("\n# Check parsing of invalid inputs\n#"); testdbPrepare(); testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); dbTestIoc_registerRecordDeviceDriver(pdbbase); testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); eltc(0); testIocInitOk(); eltc(1); for(;*td; td++) { testOk(dbParseLink(*td, DBF_INLINK, &info, "dummy","INP") == S_dbLib_badField, "dbParseLink correctly rejected \"%s\"", *td); } testIocShutdownOk(); testdbCleanup(); } static const struct testDataT { const char * const linkstring; short linkType; unsigned int pvlMask; const char * const linkback; } testSetData[] = { {"", CONSTANT, 0}, {"0", CONSTANT, 0}, {"42", CONSTANT, 0}, {"0x1", CONSTANT, 0}, {"x1", DB_LINK, 0, "x1 NPP NMS"}, {"x1.VAL", DB_LINK, 0, "x1.VAL NPP NMS"}, {"x1.TIME", DB_LINK, 0, "x1.TIME NPP NMS"}, {"x1 PP", DB_LINK, pvlOptPP, "x1 PP NMS"}, {"x1 PP MSS", DB_LINK, pvlOptPP|pvlOptMSS, "x1 PP MSS"}, {"x1 PPMSS", DB_LINK, pvlOptPP|pvlOptMSS, "x1 PP MSS"}, {"x1 PPMSI", DB_LINK, pvlOptPP|pvlOptMSI, "x1 PP MSI"}, {"qq", CA_LINK, pvlOptInpNative, "qq NPP NMS"}, {"qq MSI", CA_LINK, pvlOptInpNative|pvlOptMSI, "qq NPP MSI"}, {"qq MSICA", CA_LINK, pvlOptInpNative|pvlOptCA|pvlOptMSI, "qq CA MSI"}, {"x1 CA", CA_LINK, pvlOptInpNative|pvlOptCA, "x1 CA NMS"}, {NULL} }; static void testCADBSet(void) { const struct testDataT *td = testSetData; xRecord *prec; DBLINK *plink; testDiag("\n# Checking DB/CA link retargeting\n#"); testdbPrepare(); testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); dbTestIoc_registerRecordDeviceDriver(pdbbase); testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); eltc(0); testIocInitOk(); eltc(1); prec = (xRecord*)testdbRecordPtr("x1"); plink = &prec->lnk; for (;td->linkstring;td++) { testDiag("Trying field value \"%s\"", td->linkstring); testdbPutFieldOk("x1.LNK", DBF_STRING, td->linkstring); if (td->linkback) testdbGetFieldEqual("x1.LNK", DBF_STRING, td->linkback); else testdbGetFieldEqual("x1.LNK", DBF_STRING, td->linkstring); if (!testOk(plink->type == td->linkType, "Link type")) testDiag("Expected %d, got %d", td->linkType, plink->type); if (plink->type == td->linkType) { switch (td->linkType) { case CONSTANT: if (plink->value.constantStr) testOk1(strcmp(plink->value.constantStr, td->linkstring) == 0); else if (td->linkstring[0]=='\0') testPass("Empty String"); else testFail("oops"); break; case DB_LINK: case CA_LINK: testOk(plink->value.pv_link.pvlMask == td->pvlMask, "pvlMask %x == %x", plink->value.pv_link.pvlMask, td->pvlMask); break; } } } testIocShutdownOk(); testdbCleanup(); } typedef struct { const char * const recname; short ltype; const char * const wval; short vals[5]; const char * const parm; } testHWDataT; static const testHWDataT testHWData[] = { {"rJSON_LINK", JSON_LINK, "{x:true}", {0}, "{x:true}"}, {"rVME_IO", VME_IO, "#C100 S101 @parm VME_IO", {100, 101}, "parm VME_IO"}, {"rCAMAC_IO", CAMAC_IO, "#B11 C12 N13 A14 F15 @parm CAMAC_IO", {11, 12, 13, 14, 15}, "parm CAMAC_IO"}, {"rAB_IO", AB_IO, "#L21 A22 C23 S24 @parm AB_IO", {21, 22, 23, 24}, "parm AB_IO"}, {"rGPIB_IO", GPIB_IO, "#L31 A32 @parm GPIB_IO", {31, 32}, "parm GPIB_IO"}, {"rBITBUS_IO", BITBUS_IO, "#L41 N42 P43 S44 @parm BITBUS_IO", {41, 42, 43, 44}, "parm BITBUS_IO"}, {"rINST_IO", INST_IO, "@parm INST_IO", {0}, "parm INST_IO"}, {"rBBGPIB_IO", BBGPIB_IO, "#L51 B52 G53 @parm BBGPIB_IO", {51, 52, 53}, "parm BBGPIB_IO"}, {"rRF_IO", RF_IO, "#R61 M62 D63 E64", {61, 62, 63, 64}, NULL}, {"rVXI_IO1", VXI_IO, "#V71 C72 S73 @parm1 VXI_IO", {71, 72, 73}, "parm1 VXI_IO"}, {"rVXI_IO2", VXI_IO, "#V81 S82 @parm2 VXI_IO", {81, 82}, "parm2 VXI_IO"}, {NULL} }; static void testLink(DBLINK *plink, const testHWDataT *td) { switch(td->ltype) { case JSON_LINK: testOk1(strcmp(plink->value.json.string, td->parm) == 0); break; case VME_IO: testOk1(plink->value.vmeio.card == td->vals[0]); testOk1(plink->value.vmeio.signal == td->vals[1]); testOk1(strcmp(plink->value.vmeio.parm, td->parm) == 0); break; case CAMAC_IO: testOk1(plink->value.camacio.b == td->vals[0]); testOk1(plink->value.camacio.c == td->vals[1]); testOk1(plink->value.camacio.n == td->vals[2]); testOk1(plink->value.camacio.a == td->vals[3]); testOk1(plink->value.camacio.f == td->vals[4]); testOk1(strcmp(plink->value.camacio.parm, td->parm) == 0); break; case AB_IO: testOk1(plink->value.abio.link == td->vals[0]); testOk1(plink->value.abio.adapter == td->vals[1]); testOk1(plink->value.abio.card == td->vals[2]); testOk1(plink->value.abio.signal == td->vals[3]); testOk1(strcmp(plink->value.abio.parm, td->parm) == 0); break; case GPIB_IO: testOk1(plink->value.gpibio.link == td->vals[0]); testOk1(plink->value.gpibio.addr == td->vals[1]); testOk1(strcmp(plink->value.gpibio.parm, td->parm) == 0); break; case BITBUS_IO: testOk1(plink->value.bitbusio.link == td->vals[0]); testOk1(plink->value.bitbusio.node == td->vals[1]); testOk1(plink->value.bitbusio.port == td->vals[2]); testOk1(plink->value.bitbusio.signal == td->vals[3]); testOk1(strcmp(plink->value.bitbusio.parm, td->parm) == 0); break; case INST_IO: testOk1(strcmp(plink->value.instio.string, td->parm) == 0); break; case BBGPIB_IO: testOk1(plink->value.bbgpibio.link == td->vals[0]); testOk1(plink->value.bbgpibio.bbaddr == td->vals[1]); testOk1(plink->value.bbgpibio.gpibaddr == td->vals[2]); testOk1(strcmp(plink->value.bbgpibio.parm, td->parm) == 0); break; case RF_IO: testOk1(plink->value.rfio.cryo == td->vals[0]); testOk1(plink->value.rfio.micro == td->vals[1]); testOk1(plink->value.rfio.dataset == td->vals[2]); testOk1(plink->value.rfio.element == td->vals[3]); break; case VXI_IO: if(plink->value.vxiio.flag == VXIDYNAMIC) { testOk1(plink->value.vxiio.frame == td->vals[0]); testOk1(plink->value.vxiio.slot == td->vals[1]); testOk1(plink->value.vxiio.signal == td->vals[2]); } else { testOk1(plink->value.vxiio.la == td->vals[0]); testOk1(plink->value.vxiio.signal == td->vals[1]); } testOk1(strcmp(plink->value.vxiio.parm, td->parm) == 0); break; } } static void testHWInitSet(void) { const testHWDataT *td = testHWData; testDiag("\n# Checking HW link parsing during initialization\n#"); testdbPrepare(); testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); dbTestIoc_registerRecordDeviceDriver(pdbbase); testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); eltc(0); testIocInitOk(); eltc(1); for (;td->recname; td++) { char buf[MAX_STRING_SIZE]; xRecord *prec; DBLINK *plink; testDiag("%s == \"%s\"", td->recname, td->wval); prec = (xRecord *) testdbRecordPtr(td->recname); plink = &prec->inp; strcpy(buf, td->recname); strcat(buf, ".INP"); testdbGetFieldEqual(buf, DBR_STRING, td->wval); if (!testOk(plink->type == td->ltype, "Link type")) { testDiag("Expected %d, got %d", td->ltype, plink->type); } else { testLink(plink, td); } } testIocShutdownOk(); testdbCleanup(); } static const testHWDataT testHWData2[] = { {"rJSON_LINK", JSON_LINK, "{\"x\":true}", {0}, "{\"x\":true}"}, {"rVME_IO", VME_IO, "#C200 S201 @another VME_IO", {200, 201}, "another VME_IO"}, {"rCAMAC_IO", CAMAC_IO, "#B111 C112 N113 A114 F115 @CAMAC_IO", {111, 112, 113, 114, 115}, "CAMAC_IO"}, {"rAB_IO", AB_IO, "#L121 A122 C123 S124 @another AB_IO", {121, 122, 123, 124}, "another AB_IO"}, {"rGPIB_IO", GPIB_IO, "#L131 A132 @another GPIB_IO", {131, 132}, "another GPIB_IO"}, {"rBITBUS_IO", BITBUS_IO, "#L141 N142 P143 S144 @BITBUS_IO", {141, 142, 143, 144}, "BITBUS_IO"}, {"rINST_IO", INST_IO, "@another INST_IO", {0}, "another INST_IO"}, {"rBBGPIB_IO", BBGPIB_IO, "#L151 B152 G153 @another BBGPIB_IO", {151, 152, 153}, "another BBGPIB_IO"}, {"rRF_IO", RF_IO, "#R161 M162 D163 E164", {161, 162, 163, 164}, NULL}, {"rVXI_IO1", VXI_IO, "#V171 C172 S173 @another1 VXI_IO", {171, 172, 173}, "another1 VXI_IO"}, {"rVXI_IO2", VXI_IO, "#V181 S182 @another2 VXI_IO", {181, 182}, "another2 VXI_IO"}, {NULL} }; static void testHWMod(void) { const testHWDataT *td = testHWData2; testDiag("\n# Checking HW link parsing during retarget\n#"); testdbPrepare(); testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); dbTestIoc_registerRecordDeviceDriver(pdbbase); testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); eltc(0); testIocInitOk(); eltc(1); for(;td->recname;td++) { char buf[MAX_STRING_SIZE]; xRecord *prec; DBLINK *plink; testDiag("%s -> \"%s\"", td->recname, td->wval); prec = (xRecord*)testdbRecordPtr(td->recname); plink = &prec->inp; strcpy(buf, td->recname); strcat(buf, ".INP"); testdbPutFieldOk(buf, DBR_STRING, td->wval); testdbGetFieldEqual(buf, DBR_STRING, td->wval); if (!testOk(plink->type == td->ltype, "Link type")) { testDiag("Expected %d, got %d", td->ltype, plink->type); } else { testLink(plink, td); } } testIocShutdownOk(); testdbCleanup(); } static void testLinkInitFail(void) { xRecord *prec; DBLINK *plink; testDiag("\n# Checking link parse failures at iocInit\n#"); testdbPrepare(); testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); dbTestIoc_registerRecordDeviceDriver(pdbbase); /* this load will fail */ eltc(0); testOk(dbReadDatabase(&pdbbase, "dbBadLink.db", "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL) != 0, "dbReadDatabase returned error (expected)"); testIocInitOk(); eltc(1); testdbGetFieldEqual("eVME_IO1.INP", DBR_STRING, "#C0 S0 @"); prec = (xRecord *) testdbRecordPtr("eVME_IO1"); plink = &prec->inp; testOk1(plink->type == VME_IO); testOk1(plink->value.vmeio.parm != NULL); testdbGetFieldEqual("eVME_IO2.INP", DBR_STRING, "#C0 S0 @"); prec = (xRecord *) testdbRecordPtr("eVME_IO2"); plink = &prec->inp; testOk1(plink->type == VME_IO); testOk1(plink->value.vmeio.parm != NULL); testdbGetFieldEqual("eINST_IO.INP", DBR_STRING, "@"); prec = (xRecord *) testdbRecordPtr("eINST_IO"); plink = &prec->inp; testOk1(plink->type == INST_IO); testOk1(plink->value.instio.string != NULL); testdbGetFieldEqual("eINST_IO2.INP", DBR_STRING, "@"); prec = (xRecord *) testdbRecordPtr("eINST_IO2"); plink = &prec->inp; testOk1(plink->type == INST_IO); testOk1(plink->value.instio.string != NULL); testIocShutdownOk(); testdbCleanup(); } static void testLinkFail(void) { testDiag("\n# Checking runtime link parse failures\n#"); testdbPrepare(); testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); dbTestIoc_registerRecordDeviceDriver(pdbbase); testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); eltc(0); testIocInitOk(); eltc(1); /* INST_IO doesn't accept empty string */ testdbPutFieldFail(S_dbLib_badField, "rINST_IO.INP", DBR_STRING, ""); /* INST_IO doesn't accept string without @ */ testdbPutFieldFail(S_dbLib_badField, "rINST_IO.INP", DBR_STRING, "abc"); /* JSON_LINK dies when expected */ testdbPutFieldOk("rJSON_LINK.INP", DBR_STRING, "{\"x\":true}"); testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":false}"); testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":null}"); testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":1}"); testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":1.1}"); testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":\"x\"}"); testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":[]}"); testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":{}}"); /* JSON_LINK syntax errors */ testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":bbbb}"); /* syntax errors */ testdbPutFieldFail(S_dbLib_badField, "rVME_IO.INP", DBR_STRING, "#S201 C200 @another VME_IO"); testdbPutFieldFail(S_dbLib_badField, "rVME_IO.INP", DBR_STRING, "C200 #S201"); /* VME_IO doesn't accept empty string */ testdbPutFieldFail(S_dbLib_badField, "rVME_IO.INP", DBR_STRING, ""); testdbPutFieldOk("rVME_IO.INP", DBR_STRING, "#C1 S2 @hello"); /* VME_IO fails invalid input */ testdbPutFieldFail(S_dbLib_badField, "rVME_IO.INP", DBR_STRING, "abc"); testIocShutdownOk(); testdbCleanup(); } static void testNumZ(int expect) { int numz = epicsAtomicGetIntT(&numzalloc); testOk(numz==expect, "numzalloc==%d (%d)", expect, numz); } static void testJLink(void) { testDiag("Test json link setup/retarget"); testNumZ(0); testDiag("Link parsing failures"); testdbPrepare(); testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); dbTestIoc_registerRecordDeviceDriver(pdbbase); testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); testdbReadDatabase("dbPutLinkTestJ.db", NULL, NULL); testNumZ(0); eltc(0); testIocInitOk(); eltc(1); testNumZ(6); testdbPutFieldOk("j1.PROC", DBF_LONG, 1); testdbPutFieldOk("j2.PROC", DBF_LONG, 1); testdbPutFieldOk("j3.PROC", DBF_LONG, 1); testdbGetFieldEqual("j1.INP", DBF_STRING, "{z:{good:1}}"); testdbGetFieldEqual("j1.VAL", DBF_LONG, 1); testdbGetFieldEqual("j2.INP", DBF_STRING, "{\"z\":{'good':2}}"); testdbGetFieldEqual("j2.VAL", DBF_LONG, 2); testdbGetFieldEqual("j2.TSEL", DBF_STRING, "j1.TIME NPP NMS"); testdbGetFieldEqual("j3.VAL", DBF_LONG, 3); testNumZ(6); testdbPutFieldOk("j1.INP", DBF_STRING, "{\"z\":{\"good\":4}}"); testdbPutFieldOk("j1.PHAS", DBF_LONG, 0); testdbPutFieldOk("j1.OUTP", DBF_STRING, "{z:{good:99}}"); testdbPutFieldOk("j1.PROC", DBF_LONG, 1); testdbGetFieldEqual("j1.VAL", DBF_LONG, 4); testdbGetFieldEqual("j1.PHAS", DBF_LONG, 4); testdbPutFieldOk("j1.OUTP", DBF_STRING, ""); testdbPutFieldOk("j2.TSEL", DBF_STRING, "{'z':{good:0}}"); testdbPutFieldOk("j2.PROC", DBF_LONG, 1); testNumZ(7); testdbPutFieldFail(S_dbLib_badField, "j1.INP", DBF_STRING, "{\"z\":{\"fail\":5}}"); testdbPutFieldFail(S_dbLib_badField, "j1.INP", DBF_STRING, "{\"z\":{\"good\":6}"); testdbPutFieldOk("j1.PROC", DBF_LONG, 1); testdbGetFieldEqual("j1.VAL", DBF_LONG, 4); /* put failures in parsing stage don't modify link */ testdbGetFieldEqual("j1.INP", DBF_STRING, "{\"z\":{\"good\":4}}"); testNumZ(7); /* Check SDIS using a JSON link prevents processing */ testdbPutFieldOk("j1.SDIS", DBF_STRING, "{z:{good:1}}"); testdbPutFieldOk("j1.INP", DBF_STRING, "{z:{good:1}}"); testdbPutFieldOk("j1.PROC", DBF_LONG, 1); testdbGetFieldEqual("j1.VAL", DBF_LONG, 4); testIocShutdownOk(); testNumZ(0); testdbCleanup(); } static void testTSEL(void) { dbCommon *rec[2]; dbLocker *locker; testDiag("Test TSEL link to .TIME"); testdbPrepare(); testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); dbTestIoc_registerRecordDeviceDriver(pdbbase); testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); rec[0] = testdbRecordPtr("time:one"); rec[1] = testdbRecordPtr("time:two"); eltc(0); testIocInitOk(); eltc(1); locker = dbLockerAlloc(rec, NELEMENTS(rec), 0); if(!locker) testAbort("dbLockerAlloc() fails"); testdbPutFieldOk("time:one.PROC", DBF_LONG, 1); /* wait a bit so that we would get different timestamps */ epicsThreadSleep(0.001); testdbPutFieldOk("time:two.PROC", DBF_LONG, 1); #define COMPARE(MSG, C, TS1, TS2) testOk((C)^((TS1)->secPastEpoch == (TS2)->secPastEpoch && (TS1)->nsec == (TS2)->nsec), \ MSG " %u:%u == %u:%u", (unsigned)(TS1)->secPastEpoch, (unsigned)(TS1)->nsec, \ (unsigned)(TS2)->secPastEpoch, (unsigned)(TS2)->nsec) testDiag("Check initially connected TSEL link"); dbScanLockMany(locker); COMPARE("first", 0, &rec[0]->time, &rec[1]->time); testOk1(rec[1]->tsel.flags & DBLINK_FLAG_TSELisTIME); dbScanUnlockMany(locker); testdbPutFieldOk("time:two.TSEL", DBF_STRING, ""); testdbPutFieldOk("time:two.PROC", DBF_LONG, 1); testDiag("Check no TSEL link"); dbScanLockMany(locker); COMPARE("second", 1, &rec[0]->time, &rec[1]->time); testOk1(!(rec[1]->tsel.flags & DBLINK_FLAG_TSELisTIME)); dbScanUnlockMany(locker); testdbPutFieldOk("time:two.TSEL", DBF_STRING, "time:one.TIME"); testdbPutFieldOk("time:two.PROC", DBF_LONG, 1); testDiag("Check re-connected TSEL link"); dbScanLockMany(locker); COMPARE("third", 0, &rec[0]->time, &rec[1]->time); testOk1(rec[1]->tsel.flags & DBLINK_FLAG_TSELisTIME); dbScanUnlockMany(locker); dbLockerFree(locker); testIocShutdownOk(); testdbCleanup(); #undef COMPARE } MAIN(dbPutLinkTest) { testPlan(354); testLinkParse(); testLinkFailParse(); testCADBSet(); testHWInitSet(); testHWMod(); testLinkInitFail(); testLinkFail(); testJLink(); testTSEL(); return testDone(); }