diff --git a/modules/libcom/src/error/errlog.h b/modules/libcom/src/error/errlog.h index 32e2da7a5..4aaf054ac 100644 --- a/modules/libcom/src/error/errlog.h +++ b/modules/libcom/src/error/errlog.h @@ -285,14 +285,16 @@ LIBCOM_API void errSymLookup(long status, char *pBuf, size_t bufLength); #define ANSI_ESC_MAGENTA "\033[35;1m" #define ANSI_ESC_CYAN "\033[36;1m" #define ANSI_ESC_BOLD "\033[1m" +#define ANSI_ESC_UNDERLINE "\033[4m" #define ANSI_ESC_RESET "\033[0m" -#define ANSI_RED(STR) ANSI_ESC_RED STR ANSI_ESC_RESET -#define ANSI_GREEN(STR) ANSI_ESC_GREEN STR ANSI_ESC_RESET -#define ANSI_YELLOW(STR) ANSI_ESC_YELLOW STR ANSI_ESC_RESET -#define ANSI_BLUE(STR) ANSI_ESC_BLUE STR ANSI_ESC_RESET -#define ANSI_MAGENTA(STR) ANSI_ESC_MAGENTA STR ANSI_ESC_RESET -#define ANSI_CYAN(STR) ANSI_ESC_CYAN STR ANSI_ESC_RESET -#define ANSI_BOLD(STR) ANSI_ESC_BOLD STR ANSI_ESC_RESET +#define ANSI_RED(STR) ANSI_ESC_RED STR ANSI_ESC_RESET +#define ANSI_GREEN(STR) ANSI_ESC_GREEN STR ANSI_ESC_RESET +#define ANSI_YELLOW(STR) ANSI_ESC_YELLOW STR ANSI_ESC_RESET +#define ANSI_BLUE(STR) ANSI_ESC_BLUE STR ANSI_ESC_RESET +#define ANSI_MAGENTA(STR) ANSI_ESC_MAGENTA STR ANSI_ESC_RESET +#define ANSI_CYAN(STR) ANSI_ESC_CYAN STR ANSI_ESC_RESET +#define ANSI_BOLD(STR) ANSI_ESC_BOLD STR ANSI_ESC_RESET +#define ANSI_UNDERLINE(STR) ANSI_ESC_UNDERLINE STR ANSI_ESC_RESET #define ERL_ERROR ANSI_RED("ERROR") #define ERL_WARNING ANSI_MAGENTA("WARNING") /** @} */ diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index be4a952ee..f283739d2 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -892,15 +892,19 @@ static void helpCallFunc(const iocshArgBuf *args) "Type 'help ' to see the arguments of . eg. 'help db*'\n"); } else { + bool firstFunction = true; for (int iarg = 1 ; iarg < argc ; iarg++) { for (pcmd = iocshCommandHead ; pcmd != NULL ; pcmd = pcmd->next) { piocshFuncDef = pcmd->def.pFuncDef; if (epicsStrGlobMatch(piocshFuncDef->name, argv[iarg]) != 0) { - if(piocshFuncDef->usage) { - fputs("\nUsage: ", epicsGetStdout()); + + if (! firstFunction) { + fprintf(epicsGetStdout(), + ANSI_UNDERLINE(" \n")); } + fprintf(epicsGetStdout(), - ANSI_BOLD("%s"), + ANSI_BOLD("\n%s"), piocshFuncDef->name); for (int a = 0 ; a < piocshFuncDef->nargs ; a++) { @@ -913,11 +917,14 @@ static void helpCallFunc(const iocshArgBuf *args) fprintf(epicsGetStdout(), " '%s'", cp); } } - fprintf(epicsGetStdout(),"\n");; + fprintf(epicsGetStdout(),"\n"); if(piocshFuncDef->usage) { fprintf(epicsGetStdout(), "\n%s", piocshFuncDef->usage); } + + firstFunction = false; } + } } } diff --git a/modules/libcom/test/Makefile b/modules/libcom/test/Makefile index 809ae7338..a3893661f 100644 --- a/modules/libcom/test/Makefile +++ b/modules/libcom/test/Makefile @@ -269,6 +269,7 @@ TESTPROD_HOST += iocshTest iocshTest_SRCS += iocshTest.cpp TESTS += iocshTest TESTFILES += $(wildcard ../iocshTest*.cmd) +TESTFILES += ../iocshTestHelpFunction1 ../iocshTestHelpFunctions TESTPROD_HOST += epicsLoadTest epicsLoadTest_SRCS += epicsLoadTest.cpp diff --git a/modules/libcom/test/iocshTest.cpp b/modules/libcom/test/iocshTest.cpp index 7ca5c3867..4b98112da 100644 --- a/modules/libcom/test/iocshTest.cpp +++ b/modules/libcom/test/iocshTest.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -83,14 +85,117 @@ void assertCallFunc(const iocshArgBuf *args) iocshSetError(args[0].ival); } +const iocshFuncDef testHelpFunction1Def = {"testHelpFunction1",0,0, + "Usage message of testHelpFunction1\n"}; +const iocshFuncDef testHelpFunction2Def = {"testHelpFunction2",0,0, + "Usage message of testHelpFunction2\n"}; +const iocshFuncDef testHelpFunction3Def = {"testHelpFunction3",0,0, + "Usage message of testHelpFunction3\n"}; +void doNothing(const iocshArgBuf *args) +{ + return; +} + +std::string readFile(std::string filename) +{ + std::ifstream t(filename.c_str()); + std::stringstream buffer; + + if (!t.is_open()) { + throw std::invalid_argument("Could not open filename " + filename); + } + + buffer << t.rdbuf(); + return buffer.str(); +} + +bool compareFiles(const std::string& p1, const std::string& p2) +{ + std::ifstream f1(p1.c_str(), std::ifstream::binary|std::ifstream::ate); + std::ifstream f2(p2.c_str(), std::ifstream::binary|std::ifstream::ate); + + if (f1.fail() || f2.fail()) { + testDiag("One or more files failed to open"); + testDiag("f1.fail(): %d f2.fail(): %d", f1.fail(), f2.fail()); + return false; // File problem + } + + if (f1.tellg() != f2.tellg()) { + testDiag("File sizes did not match"); + return false; // Size mismatch + } + + // Seek back to beginning and use std::equal to compare contents + f1.seekg(0, std::ifstream::beg); + f2.seekg(0, std::ifstream::beg); + + bool are_equal = std::equal( + std::istreambuf_iterator(f1.rdbuf()), + std::istreambuf_iterator(), + std::istreambuf_iterator(f2.rdbuf()) + ); + + if (! are_equal) { + testDiag("File contents did not match"); + + std::string line; + f1.seekg(0, std::ifstream::beg); + f2.seekg(0, std::ifstream::beg); + + testDiag("File1 contents: "); + while(std::getline(f1, line)) { + testDiag("%s", line.c_str()); + } + + testDiag("File2 contents: "); + while(std::getline(f2, line)) { + testDiag("%s", line.c_str()); + } + } + + return are_equal; +} + + +void testHelp(void) +{ + testDiag("iocshTest testHelp start"); + + // Filename to save help output to + const std::string filename = "testHelpOutput"; + + // Verify help lists expected commands + iocshCmd(("help > " + filename).c_str()); + std::string contents = readFile(filename); + testOk1(contents.find("help") != std::string::npos); + testOk1(contents.find("testHelpFunction1") != std::string::npos); + testOk1(contents.find("testHelpFunction2") != std::string::npos); + testOk1(contents.find("testHelpFunction3") != std::string::npos); + + // Confirm formatting of a single command + iocshCmd(("help testHelpFunction1 > " + filename).c_str()); + testOk1(compareFiles(filename, "iocshTestHelpFunction1") == true); + + // Confirm formatting of multiple commands + iocshCmd(("help testHelp* > " + filename).c_str()); + testOk1(compareFiles(filename, "iocshTestHelpFunctions") == true); + + remove(filename.c_str()); +} + } // namespace + + MAIN(iocshTest) { - testPlan(19); + testPlan(25); libComRegister(); iocshRegister(&positionFuncDef, &positionCallFunc); iocshRegister(&assertFuncDef, &assertCallFunc); + iocshRegister(&testHelpFunction1Def, &doNothing); + iocshRegister(&testHelpFunction2Def, &doNothing); + iocshRegister(&testHelpFunction3Def, &doNothing); findTestData(); testFile("iocshTestSuccess.cmd"); @@ -129,6 +234,8 @@ MAIN(iocshTest) testPosition("after_error_1", false); reached.clear(); + testHelp(); + // cleanup after macLib to avoid valgrind false positives dbmfFreeChunks(); return testDone(); diff --git a/modules/libcom/test/iocshTestHelpFunction1 b/modules/libcom/test/iocshTestHelpFunction1 new file mode 100644 index 000000000..515d62f49 --- /dev/null +++ b/modules/libcom/test/iocshTestHelpFunction1 @@ -0,0 +1,4 @@ + +testHelpFunction1 + +Usage message of testHelpFunction1 diff --git a/modules/libcom/test/iocshTestHelpFunctions b/modules/libcom/test/iocshTestHelpFunctions new file mode 100644 index 000000000..0a6d11159 --- /dev/null +++ b/modules/libcom/test/iocshTestHelpFunctions @@ -0,0 +1,14 @@ + +testHelpFunction1 + +Usage message of testHelpFunction1 + + +testHelpFunction2 + +Usage message of testHelpFunction2 + + +testHelpFunction3 + +Usage message of testHelpFunction3