diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html
index b17f000c8..e6e3fbdc1 100644
--- a/documentation/RELEASE_NOTES.html
+++ b/documentation/RELEASE_NOTES.html
@@ -30,6 +30,26 @@ release.
-->
+Iocsh "on error ..."
+
+A new statement is added to enable IOC shell commands
+to signal error conditions, and for scripts to respond.
+This first is through the new function
iocshSetError(int)
+A script may be prefixed with eg. "on error break"
+to stop at the failed command.
+
+
+on error continue | break | wait [value] | halt
+
+
+A suggested form for IOC shell commands is:
+
+
+static void doSomethingCallFunc(const iocshArgBuf *args)
+{
+ iocshSetError(doSomething(...)); /* return 0 == success */
+}
+
Channel Access Security: Check Hostname Against DNS
diff --git a/modules/database/src/ioc/as/asIocRegister.c b/modules/database/src/ioc/as/asIocRegister.c
index 16cba90c6..d5926a551 100644
--- a/modules/database/src/ioc/as/asIocRegister.c
+++ b/modules/database/src/ioc/as/asIocRegister.c
@@ -39,7 +39,7 @@ static void asSetSubstitutionsCallFunc(const iocshArgBuf *args)
static const iocshFuncDef asInitFuncDef = {"asInit",0};
static void asInitCallFunc(const iocshArgBuf *args)
{
- asInit();
+ iocshSetError(asInit());
}
/* asdbdump */
diff --git a/modules/database/src/ioc/db/dbIocRegister.c b/modules/database/src/ioc/db/dbIocRegister.c
index c40af92c1..afb31151d 100644
--- a/modules/database/src/ioc/db/dbIocRegister.c
+++ b/modules/database/src/ioc/db/dbIocRegister.c
@@ -39,7 +39,7 @@ static const iocshFuncDef dbLoadDatabaseFuncDef =
{"dbLoadDatabase",3,dbLoadDatabaseArgs};
static void dbLoadDatabaseCallFunc(const iocshArgBuf *args)
{
- dbLoadDatabase(args[0].sval,args[1].sval,args[2].sval);
+ iocshSetError(dbLoadDatabase(args[0].sval,args[1].sval,args[2].sval));
}
/* dbLoadRecords */
@@ -49,7 +49,7 @@ static const iocshArg * const dbLoadRecordsArgs[2] = {&dbLoadRecordsArg0,&dbLoad
static const iocshFuncDef dbLoadRecordsFuncDef = {"dbLoadRecords",2,dbLoadRecordsArgs};
static void dbLoadRecordsCallFunc(const iocshArgBuf *args)
{
- dbLoadRecords(args[0].sval,args[1].sval);
+ iocshSetError(dbLoadRecords(args[0].sval,args[1].sval));
}
/* dbb */
diff --git a/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.c b/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.c
index 201a32398..879f67e19 100644
--- a/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.c
+++ b/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.c
@@ -22,7 +22,7 @@ static const iocshFuncDef dbLoadTemplateFuncDef =
{"dbLoadTemplate", 2, dbLoadTemplateArgs};
static void dbLoadTemplateCallFunc(const iocshArgBuf *args)
{
- dbLoadTemplate(args[0].sval, args[1].sval);
+ iocshSetError(dbLoadTemplate(args[0].sval, args[1].sval));
}
diff --git a/modules/database/src/ioc/misc/miscIocRegister.c b/modules/database/src/ioc/misc/miscIocRegister.c
index 6c08ef0c9..4dffdfca0 100644
--- a/modules/database/src/ioc/misc/miscIocRegister.c
+++ b/modules/database/src/ioc/misc/miscIocRegister.c
@@ -22,28 +22,28 @@
static const iocshFuncDef iocInitFuncDef = {"iocInit",0,NULL};
static void iocInitCallFunc(const iocshArgBuf *args)
{
- iocInit();
+ iocshSetError(iocInit());
}
/* iocBuild */
static const iocshFuncDef iocBuildFuncDef = {"iocBuild",0,NULL};
static void iocBuildCallFunc(const iocshArgBuf *args)
{
- iocBuild();
+ iocshSetError(iocBuild());
}
/* iocRun */
static const iocshFuncDef iocRunFuncDef = {"iocRun",0,NULL};
static void iocRunCallFunc(const iocshArgBuf *args)
{
- iocRun();
+ iocshSetError(iocRun());
}
/* iocPause */
static const iocshFuncDef iocPauseFuncDef = {"iocPause",0,NULL};
static void iocPauseCallFunc(const iocshArgBuf *args)
{
- iocPause();
+ iocshSetError(iocPause());
}
/* coreRelease */
@@ -77,7 +77,7 @@ static const iocshArg * const systemArgs[] = {&systemArg0};
static const iocshFuncDef systemFuncDef = {"system",1,systemArgs};
static void systemCallFunc(const iocshArgBuf *args)
{
- system(args[0].sval);
+ iocshSetError(system(args[0].sval));
}
#endif
diff --git a/modules/database/src/tools/registerRecordDeviceDriver.pl b/modules/database/src/tools/registerRecordDeviceDriver.pl
index acbcc517c..9e81c9875 100644
--- a/modules/database/src/tools/registerRecordDeviceDriver.pl
+++ b/modules/database/src/tools/registerRecordDeviceDriver.pl
@@ -275,7 +275,7 @@ static const iocshFuncDef rrddFuncDef =
{"$subname", 1, rrddArgs};
static void rrddCallFunc(const iocshArgBuf *)
{
- $subname(*iocshPpdbbase);
+ iocshSetError($subname(*iocshPpdbbase));
}
} // extern "C"
diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp
index 0de90c87a..a795c9b70 100644
--- a/modules/libcom/src/iocsh/iocsh.cpp
+++ b/modules/libcom/src/iocsh/iocsh.cpp
@@ -11,6 +11,8 @@
/* Heavily modified by Eric Norum Date: 03MAY2000 */
/* Adapted to C++ by Eric Norum Date: 18DEC2000 */
+#include
+
#include
#include
#include
@@ -18,6 +20,7 @@
#include
#define epicsExportSharedSymbols
+#include "epicsMath.h"
#include "errlog.h"
#include "macLib.h"
#include "epicsStdio.h"
@@ -56,7 +59,7 @@ static char iocshVarID[] = "iocshVar";
extern "C" { static void varCallFunc(const iocshArgBuf *); }
static epicsMutexId iocshTableMutex;
static epicsThreadOnceId iocshOnceId = EPICS_THREAD_ONCE_INIT;
-static epicsThreadPrivateId iocshMacroHandleId;
+static epicsThreadPrivateId iocshContextId;
/*
* I/O redirection
@@ -76,7 +79,7 @@ struct iocshRedirect {
static void iocshOnce (void *)
{
iocshTableMutex = epicsMutexMustCreate ();
- iocshMacroHandleId = epicsThreadPrivateCreate();
+ iocshContextId = epicsThreadPrivateCreate();
}
static void iocshInit (void)
@@ -496,6 +499,39 @@ static void helpCallFunc(const iocshArgBuf *args)
}
}
+typedef enum {
+ Continue,
+ Break,
+ Halt
+} OnError;
+
+// per call to iocshBody()
+struct iocshScope {
+ iocshScope *outer;
+ OnError onerr;
+ double timeout;
+ bool errored;
+ bool interactive;
+ iocshScope() :outer(0), onerr(Continue), timeout(0.0), errored(false), interactive(false) {}
+};
+
+// per thread executing iocshBody()
+struct iocshContext {
+ MAC_HANDLE *handle;
+ iocshScope *scope;
+};
+
+int iocshSetError(int err)
+{
+ iocshContext *ctxt;
+ if (err && iocshContextId) {
+ ctxt = (iocshContext *) epicsThreadPrivateGet(iocshContextId);
+
+ if(ctxt && ctxt->scope) ctxt->scope->errored = 1;
+ }
+ return err;
+}
+
/*
* The body of the command interpreter
*/
@@ -524,8 +560,10 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
void *readlineContext = NULL;
int wasOkToBlock;
static const char * pairs[] = {"", "environ", NULL, NULL};
- MAC_HANDLE *handle;
+ iocshScope scope;
+ iocshContext *context;
char ** defines = NULL;
+ int ret = 0;
iocshInit();
@@ -534,8 +572,10 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
*/
if (commandLine == NULL) {
if ((pathname == NULL) || (strcmp (pathname, "") == 0)) {
- if ((prompt = envGetConfigParamPtr(&IOCSH_PS1)) == NULL)
+ if ((prompt = envGetConfigParamPtr(&IOCSH_PS1)) == NULL) {
prompt = "epics> ";
+ }
+ scope.interactive = true;
}
else {
fp = fopen (pathname, "r");
@@ -560,6 +600,10 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
fclose(fp);
return -1;
}
+
+ } else {
+ // use of iocshCmd() implies "on error break"
+ scope.onerr = Break;
}
/*
@@ -583,21 +627,25 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
}
}
- /*
- * Check for existing macro context or construct a new one.
- */
- handle = (MAC_HANDLE *) epicsThreadPrivateGet(iocshMacroHandleId);
-
- if (handle == NULL) {
- if (macCreateHandle(&handle, pairs)) {
+ // Check for existing context or construct a new one.
+ context = (iocshContext *) epicsThreadPrivateGet(iocshContextId);
+
+ if (!context) {
+ context = (iocshContext*)calloc(1, sizeof(*context));
+ if (!context || macCreateHandle(&context->handle, pairs)) {
errlogMessage("iocsh: macCreateHandle failed.");
free(redirects);
+ free(context);
return -1;
}
- epicsThreadPrivateSet(iocshMacroHandleId, (void *) handle);
+ epicsThreadPrivateSet(iocshContextId, (void *) context);
}
-
+ MAC_HANDLE *handle = context->handle;
+
+ scope.outer = context->scope;
+ context->scope = &scope;
+
macPushScope(handle);
macInstallMacros(handle, defines);
@@ -608,6 +656,29 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
* Read commands till EOF or exit
*/
for (;;) {
+ if(!scope.interactive && scope.errored) {
+ if(scope.onerr==Continue) {
+ /* do nothing */
+
+ } else if(scope.onerr==Break) {
+ ret = -1;
+ fprintf(epicsGetStderr(), "iocsh Error: Break\n" );
+ break;
+
+ } else if(scope.onerr==Halt) {
+ ret = -1;
+ if(scope.timeout<=0.0 || isinf(scope.timeout)) {
+ fprintf(epicsGetStderr(), "iocsh Error: Halt\n" );
+ epicsThreadSuspendSelf();
+ break;
+
+ } else {
+ fprintf(epicsGetStderr(), "iocsh Error: Waiting %.1f sec ...\n", scope.timeout);
+ epicsThreadSleep(scope.timeout);
+ }
+ }
+ }
+
/*
* Read a line
*/
@@ -646,8 +717,10 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
* Expand macros
*/
free(line);
- if ((line = macDefExpand(raw, handle)) == NULL)
+ if ((line = macDefExpand(raw, handle)) == NULL) {
+ scope.errored = true;
continue;
+ }
/*
* Skip leading white-space coming from a macro
@@ -660,9 +733,11 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
* Echo non-empty lines read from a script.
* Comments delineated with '#-' aren't echoed.
*/
- if ((prompt == NULL) && *line && (commandLine == NULL))
- if ((c != '#') || (line[icin + 1] != '-'))
+ if ((prompt == NULL) && *line && (commandLine == NULL)) {
+ if ((c != '#') || (line[icin + 1] != '-')) {
puts(line);
+ }
+ }
/*
* Ignore lines that became a comment or empty after macro expansion
@@ -686,6 +761,7 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
if (newv == NULL) {
fprintf (epicsGetStderr(), "Out of memory!\n");
argc = -1;
+ scope.errored = true;
break;
}
argv = newv;
@@ -759,8 +835,9 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
}
else {
if (!sep) {
- if (((c == '"') || (c == '\'')) && !backslash)
+ if (((c == '"') || (c == '\'')) && !backslash) {
quote = c;
+ }
if (redirect != NULL) {
if (redirect->name != NULL) {
argc = -1;
@@ -781,16 +858,20 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
}
if (redirect != NULL) {
showError(filename, lineno, "Illegal redirection.");
+ scope.errored = true;
continue;
}
- if (argc < 0)
+ if (argc < 0) {
break;
+ }
if (quote != EOF) {
showError(filename, lineno, "Unbalanced quote.");
+ scope.errored = true;
continue;
}
if (backslash) {
showError(filename, lineno, "Trailing backslash.");
+ scope.errored = true;
continue;
}
if (inword)
@@ -807,7 +888,8 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
if (openRedirect(filename, lineno, redirects) < 0)
continue;
startRedirect(filename, lineno, redirects);
- iocshBody(commandFile, NULL, macros);
+ if(iocshBody(commandFile, NULL, macros))
+ scope.errored = true;
stopRedirect(filename, lineno, redirects);
continue;
}
@@ -822,6 +904,9 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
* Set up redirection
*/
if ((openRedirect(filename, lineno, redirects) == 0) && (argc > 0)) {
+ // error unless a function is actually called.
+ // handles command not-found and arg parsing errors.
+ scope.errored = true;
/*
* Look up command
*/
@@ -834,7 +919,17 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
for (int iarg = 0 ; ; ) {
if (iarg == piocshFuncDef->nargs) {
startRedirect(filename, lineno, redirects);
- (*found->def.func)(argBuf);
+ /* execute */
+ scope.errored = false;
+ try {
+ (*found->def.func)(argBuf);
+ } catch(std::exception& e){
+ fprintf(epicsGetStderr(), "c++ error: %s\n", e.what());
+ scope.errored = true;
+ } catch(...) {
+ fprintf(epicsGetStderr(), "c++ error unknown\n");
+ scope.errored = true;
+ }
break;
}
if (iarg >= argBufCapacity) {
@@ -874,9 +969,12 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
}
macPopScope(handle);
- if (handle->level == 0) {
+ if (!scope.outer) {
macDeleteHandle(handle);
- epicsThreadPrivateSet(iocshMacroHandleId, NULL);
+ free(context);
+ epicsThreadPrivateSet(iocshContextId, NULL);
+ } else {
+ context->scope = scope.outer;
}
if (fp && (fp != stdin))
fclose (fp);
@@ -891,7 +989,7 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
if (readlineContext)
epicsReadlineEnd(readlineContext);
epicsThreadSetOkToBlock(wasOkToBlock);
- return 0;
+ return ret;
}
/*
@@ -943,13 +1041,13 @@ iocshRun(const char *cmd, const char *macros)
void epicsShareAPI
iocshEnvClear(const char *name)
{
- MAC_HANDLE *handle;
+ iocshContext *context;
- if (iocshMacroHandleId) {
- handle = (MAC_HANDLE *) epicsThreadPrivateGet(iocshMacroHandleId);
+ if (iocshContextId) {
+ context = (iocshContext *) epicsThreadPrivateGet(iocshContextId);
- if (handle != NULL) {
- macPutValue(handle, name, NULL);
+ if (context != NULL) {
+ macPutValue(context->handle, name, NULL);
}
}
}
@@ -1054,6 +1152,59 @@ static void iocshRunCallFunc(const iocshArgBuf *args)
iocshRun(args[0].sval, args[1].sval);
}
+/* on */
+static const iocshArg onArg0 = { "'error' 'continue' | 'break' | 'wait' [value] | 'halt'", iocshArgArgv };
+static const iocshArg *onArgs[1] = {&onArg0};
+static const iocshFuncDef onFuncDef = {"on", 1, onArgs};
+static void onCallFunc(const iocshArgBuf *args)
+{
+ iocshContext *context = (iocshContext *) epicsThreadPrivateGet(iocshContextId);
+
+#define USAGE() fprintf(epicsGetStderr(), "Usage: on error [continue | break | halt | wait ]\n")
+
+ if(!context || !context->scope) {
+ // we are not called through iocshBody()...
+
+ } else if(args->aval.ac<3 || strcmp(args->aval.av[1], "error")!=0) {
+ USAGE();
+
+ } else if(context->scope->interactive) {
+ fprintf(epicsGetStderr(), "Interactive shell ignores on error ...\n");
+
+ } else {
+ // don't fault on previous, ignored, errors
+ context->scope->errored = false;
+
+ if(strcmp(args->aval.av[2], "continue")==0) {
+ context->scope->onerr = Continue;
+
+ } else if(strcmp(args->aval.av[2], "break")==0) {
+ context->scope->onerr = Break;
+
+ } else if(strcmp(args->aval.av[2], "halt")==0) {
+ context->scope->onerr = Halt;
+ context->scope->timeout = 0.0;
+
+ } else if(strcmp(args->aval.av[2], "wait")==0) {
+ context->scope->onerr = Halt;
+ if(args->aval.ac<=3) {
+ USAGE();
+ } else if(epicsParseDouble(args->aval.av[3], &context->scope->timeout, NULL)) {
+ context->scope->timeout = 5.0;
+ } else {
+ USAGE();
+ fprintf(epicsGetStderr(), "Unable to parse 'on error wait' time %s\n", args->aval.av[3]);
+ }
+
+ } else {
+ fprintf(epicsGetStderr(), "Usage: on error [continue | break | halt | wait ]\n");
+ context->scope->errored = true;
+ }
+ }
+
+#undef USAGE
+}
+
/*
* Dummy internal commands -- register and install in command table
* so they show up in the help display
@@ -1083,6 +1234,7 @@ static void localRegister (void)
iocshRegister(&iocshCmdFuncDef,iocshCmdCallFunc);
iocshRegister(&iocshLoadFuncDef,iocshLoadCallFunc);
iocshRegister(&iocshRunFuncDef,iocshRunCallFunc);
+ iocshRegister(&onFuncDef, onCallFunc);
}
} /* extern "C" */
diff --git a/modules/libcom/src/iocsh/iocsh.h b/modules/libcom/src/iocsh/iocsh.h
index 84b38f224..a63af64ce 100644
--- a/modules/libcom/src/iocsh/iocsh.h
+++ b/modules/libcom/src/iocsh/iocsh.h
@@ -76,7 +76,7 @@ epicsShareFunc void epicsShareAPI iocshRegister(
epicsShareFunc void epicsShareAPI iocshRegisterVariable (
const iocshVarDef *piocshVarDef);
epicsShareFunc const iocshCmdDef * epicsShareAPI iocshFindCommand(
- const char* name);
+ const char* name) EPICS_DEPRECATED;
epicsShareFunc const iocshVarDef * epicsShareAPI iocshFindVariable(
const char* name);
@@ -84,11 +84,30 @@ epicsShareFunc const iocshVarDef * epicsShareAPI iocshFindVariable(
/* This should only be called when iocsh is no longer needed*/
epicsShareFunc void epicsShareAPI iocshFree(void);
+/** shorthand for @code iocshLoad(pathname, NULL) @endcode */
epicsShareFunc int epicsShareAPI iocsh(const char *pathname);
+/** shorthand for @code iocshRun(cmd, NULL) @endcode */
epicsShareFunc int epicsShareAPI iocshCmd(const char *cmd);
+/** Read and evaluate IOC shell commands from the given file.
+ * @param pathname Path to script file
+ * @param macros NULL or a comma seperated list of macro definitions. eg. "VAR1=x,VAR2=y"
+ * @return 0 on success, non-zero on error
+ */
epicsShareFunc int epicsShareAPI iocshLoad(const char *pathname, const char* macros);
+/** Evaluate a single IOC shell command
+ * @param cmd Command string. eg. "echo \"something or other\""
+ * @param macros NULL or a comma seperated list of macro definitions. eg. "VAR1=x,VAR2=y"
+ * @return 0 on success, non-zero on error
+ */
epicsShareFunc int epicsShareAPI iocshRun(const char *cmd, const char* macros);
+/** @brief Signal error from an IOC shell function.
+ *
+ * @param err 0 - success (no op), !=0 - error
+ * @return The err argument value.
+ */
+epicsShareFunc int iocshSetError(int err);
+
/* Makes macros that shadow environment variables work correctly with epicsEnvSet */
epicsShareFunc void epicsShareAPI iocshEnvClear(const char *name);
diff --git a/modules/libcom/src/iocsh/libComRegister.c b/modules/libcom/src/iocsh/libComRegister.c
index 04628bb53..c67804aef 100644
--- a/modules/libcom/src/iocsh/libComRegister.c
+++ b/modules/libcom/src/iocsh/libComRegister.c
@@ -77,7 +77,7 @@ static const iocshFuncDef chdirFuncDef = {"cd",1,chdirArgs};
static void chdirCallFunc(const iocshArgBuf *args)
{
if (args[0].sval == NULL ||
- chdir(args[0].sval)) {
+ iocshSetError(chdir(args[0].sval))) {
fprintf(stderr, "Invalid directory path, ignored\n");
}
}
diff --git a/modules/libcom/test/Makefile b/modules/libcom/test/Makefile
index 7fde7313e..5b8ab6046 100755
--- a/modules/libcom/test/Makefile
+++ b/modules/libcom/test/Makefile
@@ -246,6 +246,11 @@ TESTS += yajlTest
endif
endif
+TESTPROD_HOST += iocshTest
+iocshTest_SRCS += iocshTest.cpp
+TESTS += iocshTest
+TESTFILES += $(wildcard ../iocshTest*.cmd)
+
# The testHarness runs all the test programs in a known working order.
testHarness_SRCS += epicsRunLibComTests.c
@@ -305,3 +310,6 @@ endif
endif
include $(TOP)/configure/RULES
+
+rtemsTestData.c : $(TESTFILES) $(TOOLS)/epicsMakeMemFs.pl
+ $(PERL) $(TOOLS)/epicsMakeMemFs.pl $@ epicsRtemsFSImage $(TESTFILES)
diff --git a/modules/libcom/test/iocshTest.cpp b/modules/libcom/test/iocshTest.cpp
new file mode 100644
index 000000000..1216ed579
--- /dev/null
+++ b/modules/libcom/test/iocshTest.cpp
@@ -0,0 +1,131 @@
+/*************************************************************************\
+* Copyright (c) 2019 Michael Davidsaver
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+namespace {
+void findTestData()
+{
+ const char *locations[] = {
+ ".",
+ "..",
+ ".." OSI_PATH_LIST_SEPARATOR "O.Common",
+ "O.Common",
+ };
+
+ for(size_t i=0; i %d", expect ? "ran" : "expected error from", fname, err);
+}
+
+void testCmd(const char *cmd, bool expect=true)
+{
+ testDiag("eval \"%s\"", cmd);
+ int err = iocshCmd(cmd);
+ testOk((err==0) ^ (!expect), "%s \"%s\" -> %d", expect ? "eval'd" : "expected error from", cmd, err);
+}
+
+std::set reached;
+const iocshArg positionArg0 = {"position", iocshArgString};
+const iocshArg * const positionArgs[1] = { &positionArg0 };
+const iocshFuncDef positionFuncDef = {"position",1,positionArgs};
+void positionCallFunc(const iocshArgBuf *args)
+{
+ testDiag("Reaching \"%s\"", args[0].sval);
+ reached.insert(args[0].sval);
+}
+
+void testPosition(const std::string& pos, bool expect=true)
+{
+ testOk((reached.find(pos)!=reached.end()) ^ !expect,
+ "%sreached position %s", expect ? "" : "not ", pos.c_str());
+}
+
+const iocshArg assertArg0 = {"condition", iocshArgInt};
+const iocshArg * const assertArgs[1] = {&assertArg0};
+const iocshFuncDef assertFuncDef = {"assert",1,assertArgs};
+void assertCallFunc(const iocshArgBuf *args)
+{
+ iocshSetError(args[0].ival);
+}
+
+} // namespace
+
+MAIN(iocshTest)
+{
+ testPlan(19);
+ libComRegister();
+ iocshRegister(&positionFuncDef, &positionCallFunc);
+ iocshRegister(&assertFuncDef, &assertCallFunc);
+ findTestData();
+
+ testFile("iocshTestSuccess.cmd");
+ testPosition("success");
+ reached.clear();
+ testPosition("success", false);
+
+ testCmd("