Support testing applications
This commit is contained in:
parent
822d77484b
commit
17d22be584
|
@ -9,9 +9,24 @@ endif()
|
|||
|
||||
# add test executable, but exclude it from the "all target"
|
||||
add_executable(${META_PROJECT_NAME}_tests EXCLUDE_FROM_ALL ${TEST_HEADER_FILES} ${TEST_SRC_FILES})
|
||||
target_link_libraries(${META_PROJECT_NAME}_tests ${META_PROJECT_NAME} ${TEST_LIBRARIES} cppunit)
|
||||
|
||||
# always link test applications against c++utilities, cppunit and pthreads
|
||||
find_library(CPP_UNIT_LIB cppunit)
|
||||
find_library(PTHREAD_LIB pthread)
|
||||
list(APPEND TEST_LIBRARIES ${CPP_UTILITIES_SHARED_LIB} ${CPP_UNIT_LIB} ${PTHREAD_LIB})
|
||||
|
||||
# test applications of my projects always use c++utilities and cppunit
|
||||
if(NOT META_PROJECT_TYPE OR "${META_PROJECT_TYPE}" STREQUAL "library") # default project type is library
|
||||
# when testing a library, the test application always needs to link against it
|
||||
list(APPEND TEST_LIBRARIES ${META_PROJECT_NAME})
|
||||
else()
|
||||
# otherwise, the tests application needs the path of the application to be tested
|
||||
set(APPLICATION_PATH "-a ${CMAKE_CURRENT_BINARY_DIR}/${META_PROJECT_NAME}")
|
||||
endif()
|
||||
|
||||
target_link_libraries(${META_PROJECT_NAME}_tests ${TEST_LIBRARIES})
|
||||
set_target_properties(${META_PROJECT_NAME}_tests PROPERTIES CXX_STANDARD 11)
|
||||
add_test(NAME ${META_PROJECT_NAME}_cppunit COMMAND ${META_PROJECT_NAME}_tests -p "${CMAKE_CURRENT_SOURCE_DIR}/testfiles")
|
||||
add_test(NAME ${META_PROJECT_NAME}_cppunit COMMAND ${META_PROJECT_NAME}_tests -p "${CMAKE_CURRENT_SOURCE_DIR}/testfiles" ${APPLICATION_PATH})
|
||||
|
||||
# add the test executable to the dependencies of the check target
|
||||
add_dependencies(check ${META_PROJECT_NAME}_tests)
|
||||
|
|
|
@ -8,8 +8,14 @@
|
|||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <initializer_list>
|
||||
#include <thread>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#ifdef PLATFORM_UNIX
|
||||
# include <unistd.h>
|
||||
# include <sys/wait.h>
|
||||
# include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace ApplicationUtilities;
|
||||
|
@ -36,6 +42,7 @@ TestApplication *TestApplication::m_instance = nullptr;
|
|||
TestApplication::TestApplication(int argc, char **argv) :
|
||||
m_helpArg(m_parser),
|
||||
m_testFilesPathArg("test-files-path", 'p', "specifies the path of the directory with test files"),
|
||||
m_applicationPathArg("app-path",'a', "specifies the path of the application to be tested"),
|
||||
m_workingDirArg("working-dir", 'w', "specifies the directory to store working copies of test files"),
|
||||
m_unitsArg("units", 'u', "specifies the units to test; omit to test all units")
|
||||
{
|
||||
|
@ -55,16 +62,15 @@ TestApplication::TestApplication(int argc, char **argv) :
|
|||
}
|
||||
|
||||
// setup argument parser
|
||||
m_testFilesPathArg.setRequiredValueCount(1);
|
||||
m_testFilesPathArg.setValueNames({"path"});
|
||||
m_testFilesPathArg.setCombinable(true);
|
||||
m_workingDirArg.setRequiredValueCount(1);
|
||||
m_workingDirArg.setValueNames({"path"});
|
||||
m_workingDirArg.setCombinable(true);
|
||||
for(Argument *arg : initializer_list<Argument *>{&m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg}) {
|
||||
arg->setRequiredValueCount(1);
|
||||
arg->setValueNames({"path"});
|
||||
arg->setCombinable(true);
|
||||
}
|
||||
m_unitsArg.setRequiredValueCount(-1);
|
||||
m_unitsArg.setValueNames({"unit1", "unit2", "unit3"});
|
||||
m_unitsArg.setCombinable(true);
|
||||
m_parser.setMainArguments({&m_testFilesPathArg, &m_workingDirArg, &m_unitsArg, &m_helpArg});
|
||||
m_parser.setMainArguments({&m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg, &m_unitsArg, &m_helpArg});
|
||||
|
||||
// parse arguments
|
||||
try {
|
||||
|
@ -157,6 +163,7 @@ string TestApplication::testFilePath(const string &name) const
|
|||
#ifdef PLATFORM_UNIX
|
||||
/*!
|
||||
* \brief Returns the full path to a working copy of the test file with the specified \a name.
|
||||
* \remarks Currently only available under UNIX.
|
||||
*/
|
||||
string TestApplication::workingCopyPath(const string &name) const
|
||||
{
|
||||
|
@ -205,6 +212,63 @@ string TestApplication::workingCopyPath(const string &name) const
|
|||
}
|
||||
return string();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Executes the application to be tested with the specified \a args and stores the standard output and
|
||||
* errors in \a stdout and \a stderr.
|
||||
* \throws Throws std::runtime_error when the application can not be executed.
|
||||
* \remarks
|
||||
* - The specified \a args must be 0 terminated. The first argument is the application name.
|
||||
* - Currently only available under UNIX.
|
||||
*/
|
||||
int TestApplication::execApp(const char *const *args, string &stdout, string &stderr) const
|
||||
{
|
||||
// determine application path
|
||||
const char *appPath = m_applicationPathArg.firstValue();
|
||||
if(!appPath || !*appPath) {
|
||||
throw runtime_error("Unable to execute application to be tested: no application path specified");
|
||||
}
|
||||
// create pipes
|
||||
int coutPipes[2], cerrPipes[2];
|
||||
pipe(coutPipes), pipe(cerrPipes);
|
||||
int readCoutPipe = coutPipes[0], writeCoutPipe = coutPipes[1];
|
||||
int readCerrPipe = cerrPipes[0], writeCerrPipe = cerrPipes[1];
|
||||
// create child process
|
||||
if(int child = fork()) {
|
||||
// parent process: read stdout and stderr from child
|
||||
close(writeCoutPipe), close(writeCerrPipe);
|
||||
thread readCoutThread([readCoutPipe, &stdout] {
|
||||
char buffer[512];
|
||||
ssize_t count;
|
||||
stdout.clear();
|
||||
while((count = read(readCoutPipe, buffer, sizeof(buffer))) > 0) {
|
||||
stdout.append(buffer, count);
|
||||
}
|
||||
close(readCoutPipe);
|
||||
});
|
||||
thread readCerrThread([readCerrPipe, &stderr] {
|
||||
char buffer[512];
|
||||
ssize_t count;
|
||||
stderr.clear();
|
||||
while((count = read(readCerrPipe, buffer, sizeof(buffer))) > 0) {
|
||||
stderr.append(buffer, count);
|
||||
}
|
||||
close(readCerrPipe);
|
||||
});
|
||||
readCoutThread.join();
|
||||
readCerrThread.join();
|
||||
int childReturnCode;
|
||||
waitpid(child, &childReturnCode, 0);
|
||||
return childReturnCode;
|
||||
} else {
|
||||
// child process: set pipes to be used for stdout/stderr, execute application
|
||||
dup2(writeCoutPipe, STDOUT_FILENO), dup2(writeCerrPipe, STDERR_FILENO);
|
||||
close(readCoutPipe), close(writeCoutPipe), close(readCerrPipe), close(writeCerrPipe);
|
||||
execv(appPath, const_cast<char *const *>(args));
|
||||
cerr << "Unable to execute \"" << appPath << "\": execv() failed" << endl;
|
||||
exit(-101);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ public:
|
|||
std::string testFilePath(const std::string &name) const;
|
||||
#ifdef PLATFORM_UNIX
|
||||
std::string workingCopyPath(const std::string &name) const;
|
||||
int execApp(const char *const *args, std::string &output, std::string &errors) const;
|
||||
#endif
|
||||
bool unitsSpecified() const;
|
||||
const std::vector<const char *> &units() const;
|
||||
|
@ -27,6 +28,7 @@ private:
|
|||
ApplicationUtilities::ArgumentParser m_parser;
|
||||
ApplicationUtilities::HelpArgument m_helpArg;
|
||||
ApplicationUtilities::Argument m_testFilesPathArg;
|
||||
ApplicationUtilities::Argument m_applicationPathArg;
|
||||
ApplicationUtilities::Argument m_workingDirArg;
|
||||
ApplicationUtilities::Argument m_unitsArg;
|
||||
std::string m_testFilesPathArgValue;
|
||||
|
@ -75,6 +77,7 @@ inline const std::vector<const char *> &TestApplication::units() const
|
|||
/*!
|
||||
* \brief Convenience function which returns the full path of the test file with the specified \a name.
|
||||
* \remarks A TestApplication must be present.
|
||||
* \sa TestApplication::testFilePath()
|
||||
*/
|
||||
inline LIB_EXPORT std::string testFilePath(const std::string &name)
|
||||
{
|
||||
|
@ -85,11 +88,22 @@ inline LIB_EXPORT std::string testFilePath(const std::string &name)
|
|||
/*!
|
||||
* \brief Convenience function which returns the full path to a working copy of the test file with the specified \a name.
|
||||
* \remarks A TestApplication must be present.
|
||||
* \sa TestApplication::workingCopyPath()
|
||||
*/
|
||||
inline LIB_EXPORT std::string workingCopyPath(const std::string &name)
|
||||
{
|
||||
return TestApplication::instance()->workingCopyPath(name);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Convenience function which executes the application to be tested with the specified \a args.
|
||||
* \remarks A TestApplication must be present.
|
||||
* \sa TestApplication::execApp()
|
||||
*/
|
||||
inline LIB_EXPORT int execApp(const char *const *args, std::string &output, std::string &errors)
|
||||
{
|
||||
return TestApplication::instance()->execApp(args, output, errors);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
|
|
Loading…
Reference in New Issue