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 test executable, but exclude it from the "all target"
|
||||||
add_executable(${META_PROJECT_NAME}_tests EXCLUDE_FROM_ALL ${TEST_HEADER_FILES} ${TEST_SRC_FILES})
|
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)
|
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 the test executable to the dependencies of the check target
|
||||||
add_dependencies(check ${META_PROJECT_NAME}_tests)
|
add_dependencies(check ${META_PROJECT_NAME}_tests)
|
||||||
|
|
|
@ -8,8 +8,14 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#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 std;
|
||||||
using namespace ApplicationUtilities;
|
using namespace ApplicationUtilities;
|
||||||
|
@ -36,6 +42,7 @@ TestApplication *TestApplication::m_instance = nullptr;
|
||||||
TestApplication::TestApplication(int argc, char **argv) :
|
TestApplication::TestApplication(int argc, char **argv) :
|
||||||
m_helpArg(m_parser),
|
m_helpArg(m_parser),
|
||||||
m_testFilesPathArg("test-files-path", 'p', "specifies the path of the directory with test files"),
|
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_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")
|
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
|
// setup argument parser
|
||||||
m_testFilesPathArg.setRequiredValueCount(1);
|
for(Argument *arg : initializer_list<Argument *>{&m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg}) {
|
||||||
m_testFilesPathArg.setValueNames({"path"});
|
arg->setRequiredValueCount(1);
|
||||||
m_testFilesPathArg.setCombinable(true);
|
arg->setValueNames({"path"});
|
||||||
m_workingDirArg.setRequiredValueCount(1);
|
arg->setCombinable(true);
|
||||||
m_workingDirArg.setValueNames({"path"});
|
}
|
||||||
m_workingDirArg.setCombinable(true);
|
|
||||||
m_unitsArg.setRequiredValueCount(-1);
|
m_unitsArg.setRequiredValueCount(-1);
|
||||||
m_unitsArg.setValueNames({"unit1", "unit2", "unit3"});
|
m_unitsArg.setValueNames({"unit1", "unit2", "unit3"});
|
||||||
m_unitsArg.setCombinable(true);
|
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
|
// parse arguments
|
||||||
try {
|
try {
|
||||||
|
@ -157,6 +163,7 @@ string TestApplication::testFilePath(const string &name) const
|
||||||
#ifdef PLATFORM_UNIX
|
#ifdef PLATFORM_UNIX
|
||||||
/*!
|
/*!
|
||||||
* \brief Returns the full path to a working copy of the test file with the specified \a name.
|
* \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
|
string TestApplication::workingCopyPath(const string &name) const
|
||||||
{
|
{
|
||||||
|
@ -205,6 +212,63 @@ string TestApplication::workingCopyPath(const string &name) const
|
||||||
}
|
}
|
||||||
return string();
|
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
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ public:
|
||||||
std::string testFilePath(const std::string &name) const;
|
std::string testFilePath(const std::string &name) const;
|
||||||
#ifdef PLATFORM_UNIX
|
#ifdef PLATFORM_UNIX
|
||||||
std::string workingCopyPath(const std::string &name) const;
|
std::string workingCopyPath(const std::string &name) const;
|
||||||
|
int execApp(const char *const *args, std::string &output, std::string &errors) const;
|
||||||
#endif
|
#endif
|
||||||
bool unitsSpecified() const;
|
bool unitsSpecified() const;
|
||||||
const std::vector<const char *> &units() const;
|
const std::vector<const char *> &units() const;
|
||||||
|
@ -27,6 +28,7 @@ private:
|
||||||
ApplicationUtilities::ArgumentParser m_parser;
|
ApplicationUtilities::ArgumentParser m_parser;
|
||||||
ApplicationUtilities::HelpArgument m_helpArg;
|
ApplicationUtilities::HelpArgument m_helpArg;
|
||||||
ApplicationUtilities::Argument m_testFilesPathArg;
|
ApplicationUtilities::Argument m_testFilesPathArg;
|
||||||
|
ApplicationUtilities::Argument m_applicationPathArg;
|
||||||
ApplicationUtilities::Argument m_workingDirArg;
|
ApplicationUtilities::Argument m_workingDirArg;
|
||||||
ApplicationUtilities::Argument m_unitsArg;
|
ApplicationUtilities::Argument m_unitsArg;
|
||||||
std::string m_testFilesPathArgValue;
|
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.
|
* \brief Convenience function which returns the full path of the test file with the specified \a name.
|
||||||
* \remarks A TestApplication must be present.
|
* \remarks A TestApplication must be present.
|
||||||
|
* \sa TestApplication::testFilePath()
|
||||||
*/
|
*/
|
||||||
inline LIB_EXPORT std::string testFilePath(const std::string &name)
|
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.
|
* \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.
|
* \remarks A TestApplication must be present.
|
||||||
|
* \sa TestApplication::workingCopyPath()
|
||||||
*/
|
*/
|
||||||
inline LIB_EXPORT std::string workingCopyPath(const std::string &name)
|
inline LIB_EXPORT std::string workingCopyPath(const std::string &name)
|
||||||
{
|
{
|
||||||
return TestApplication::instance()->workingCopyPath(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
|
#endif
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
Loading…
Reference in New Issue