Support `execApp()` test helper under Windows as well via Boost.Process
This commit is contained in:
parent
18d92fee40
commit
af200403de
|
@ -128,6 +128,7 @@ include(3rdParty)
|
|||
use_iconv(AUTO_LINKAGE REQUIRED)
|
||||
|
||||
# configure use of native file buffer and its backend implementation if enabled
|
||||
set(REQUIRED_BOOST_COMPONENTS "")
|
||||
set(USE_NATIVE_FILE_BUFFER_BY_DEFAULT OFF)
|
||||
if (WIN32
|
||||
OR ANDROID
|
||||
|
@ -157,7 +158,7 @@ if (USE_NATIVE_FILE_BUFFER)
|
|||
endforeach ()
|
||||
else ()
|
||||
message(STATUS "Using boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_sink> for NativeFileStream")
|
||||
use_package(TARGET_NAME Boost::iostreams PACKAGE_NAME Boost PACKAGE_ARGS "REQUIRED;COMPONENTS;iostreams")
|
||||
list(APPEND REQUIRED_BOOST_COMPONENTS iostreams)
|
||||
foreach (NATIVE_FILE_STREAM_IMPL_FILE ${NATIVE_FILE_STREAM_IMPL_FILES})
|
||||
set_property(
|
||||
SOURCE ${NATIVE_FILE_STREAM_IMPL_FILE}
|
||||
|
@ -169,6 +170,26 @@ else ()
|
|||
message(STATUS "Using std::fstream for NativeFileStream")
|
||||
endif ()
|
||||
|
||||
# configure use of Boost.Process for launching test applications on Windows
|
||||
if (WIN32)
|
||||
option(USE_BOOST_PROCESS "enables use of Boost.Process to launch test applications" ON)
|
||||
if (USE_BOOST_PROCESS)
|
||||
list(APPEND REQUIRED_BOOST_COMPONENTS filesystem)
|
||||
list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_BOOST_PROCESS)
|
||||
list(APPEND PRIVATE_LIBRARIES ws2_32) # needed by Boost.Asio
|
||||
use_package(TARGET_NAME Threads::Threads PACKAGE_NAME Threads PACKAGE_ARGS REQUIRED)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# configure usage of Boost
|
||||
if (REQUIRED_BOOST_COMPONENTS)
|
||||
set(BOOST_ARGS REQUIRED COMPONENTS ${REQUIRED_BOOST_COMPONENTS})
|
||||
use_package(TARGET_NAME Boost::boost PACKAGE_NAME Boost PACKAGE_ARGS "${BOOST_ARGS}")
|
||||
foreach (COMPONENT ${REQUIRED_BOOST_COMPONENTS})
|
||||
use_package(TARGET_NAME Boost::${COMPONENT} PACKAGE_NAME Boost PACKAGE_ARGS "${BOOST_ARGS}")
|
||||
endforeach ()
|
||||
endif ()
|
||||
|
||||
# configure required libraries for std::filesystem
|
||||
option(USE_STANDARD_FILESYSTEM "uses std::filesystem; if disabled Bash completion for files and directories is not working"
|
||||
ON)
|
||||
|
|
|
@ -76,7 +76,8 @@ These build instructions apply to `c++utilities` but also to my other projects u
|
|||
- libstdc++ under GNU/Linux and Windows
|
||||
- libc++ under GNU/Linux and Android
|
||||
* glibc with iconv support or standalone iconv library
|
||||
* libstdc++ or Boost.Iostreams for `NativeFileStream` (optional)
|
||||
* libstdc++ or Boost.Iostreams for `NativeFileStream` (optional, use `USE_NATIVE_FILE_BUFFER` to disable)
|
||||
* Boost.Process for `execApp()` test helper under Windows (optional, use `USE_BOOST_PROCESS=OFF` to disable)
|
||||
* My other projects have further dependencies such as Qt. Checkout the README of these
|
||||
projects for further details.
|
||||
|
||||
|
|
|
@ -27,6 +27,19 @@
|
|||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef CPP_UTILITIES_BOOST_PROCESS
|
||||
#include <boost/asio/buffers_iterator.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/streambuf.hpp>
|
||||
#include <boost/process/async.hpp>
|
||||
#include <boost/process/environment.hpp>
|
||||
#include <boost/process/env.hpp>
|
||||
#include <boost/process/child.hpp>
|
||||
#include <boost/process/group.hpp>
|
||||
#include <boost/process/io.hpp>
|
||||
#include <boost/process/search_path.hpp>
|
||||
#endif
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
@ -391,7 +404,16 @@ string TestApplication::workingCopyPathAs(
|
|||
return workingCopyPath;
|
||||
}
|
||||
|
||||
#ifdef PLATFORM_UNIX
|
||||
#ifdef CPP_UTILITIES_HAS_EXEC_APP
|
||||
|
||||
#if defined(CPP_UTILITIES_BOOST_PROCESS)
|
||||
inline static std::string streambufToString(boost::asio::streambuf &buf)
|
||||
{
|
||||
const auto begin = boost::asio::buffers_begin(buf.data());
|
||||
return std::string(begin, begin + static_cast<std::ptrdiff_t>(buf.size()));
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief Executes an application with the specified \a args.
|
||||
* \remarks Provides internal implementation of execApp() and execHelperApp().
|
||||
|
@ -411,6 +433,34 @@ static int execAppInternal(const char *appPath, const char *const *args, std::st
|
|||
cout << endl;
|
||||
}
|
||||
|
||||
#if defined(CPP_UTILITIES_BOOST_PROCESS)
|
||||
auto path = enableSearchPath ? boost::process::search_path(appPath) : boost::process::filesystem::path(appPath);
|
||||
auto ctx = boost::asio::io_context();
|
||||
auto group = boost::process::group();
|
||||
auto argsAsVector = std::vector<std::string>();
|
||||
if (*args) {
|
||||
for (const char *const *arg = args + 1; *arg; ++arg) {
|
||||
argsAsVector.emplace_back(*arg);
|
||||
}
|
||||
}
|
||||
auto outputBuffer = boost::asio::streambuf(), errorBuffer = boost::asio::streambuf();
|
||||
auto env = boost::process::environment(boost::this_process::environment());
|
||||
if (!newProfilingPath.empty()) {
|
||||
env["LLVM_PROFILE_FILE"] = newProfilingPath;
|
||||
}
|
||||
auto child = boost::process::child(ctx, group, path, argsAsVector, env, boost::process::std_out > outputBuffer, boost::process::std_err > errorBuffer);
|
||||
if (timeout > 0) {
|
||||
ctx.run_for(std::chrono::milliseconds(timeout));
|
||||
} else {
|
||||
ctx.run();
|
||||
}
|
||||
output = streambufToString(outputBuffer);
|
||||
errors = streambufToString(errorBuffer);
|
||||
child.wait();
|
||||
group.wait();
|
||||
return child.exit_code();
|
||||
|
||||
#elif defined(PLATFORM_UNIX)
|
||||
// create pipes
|
||||
int coutPipes[2], cerrPipes[2];
|
||||
pipe(coutPipes);
|
||||
|
@ -504,6 +554,10 @@ static int execAppInternal(const char *appPath, const char *const *args, std::st
|
|||
cerr << Phrases::Error << "Unable to execute \"" << appPath << "\": execv() failed" << Phrases::EndFlush;
|
||||
exit(-101);
|
||||
}
|
||||
|
||||
#else
|
||||
throw std::runtime_error("lauching test applications is not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -594,7 +648,7 @@ int execHelperAppInSearchPath(
|
|||
{
|
||||
return execAppInternal(appName, args, output, errors, suppressLogging, timeout, string(), true);
|
||||
}
|
||||
#endif // PLATFORM_UNIX
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief Reads the path of the test file directory from the environment variable TEST_FILE_PATH.
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#if defined(PLATFORM_UNIX) || defined(CPP_UTILITIES_BOOST_PROCESS)
|
||||
#define CPP_UTILITIES_HAS_EXEC_APP
|
||||
#endif
|
||||
|
||||
namespace CppUtilities {
|
||||
|
||||
/*!
|
||||
|
@ -34,7 +38,7 @@ public:
|
|||
std::string workingCopyPath(const std::string &relativeTestFilePath, WorkingCopyMode mode = WorkingCopyMode::CreateCopy) const;
|
||||
std::string workingCopyPathAs(const std::string &relativeTestFilePath, const std::string &relativeWorkingCopyPath,
|
||||
WorkingCopyMode mode = WorkingCopyMode::CreateCopy) const;
|
||||
#ifdef PLATFORM_UNIX
|
||||
#ifdef CPP_UTILITIES_HAS_EXEC_APP
|
||||
int execApp(const char *const *args, std::string &output, std::string &errors, bool suppressLogging = false, int timeout = -1) const;
|
||||
#endif
|
||||
|
||||
|
@ -180,7 +184,7 @@ inline CPP_UTILITIES_EXPORT std::string workingCopyPathAs(
|
|||
return TestApplication::instance()->workingCopyPathAs(relativeTestFilePath, relativeWorkingCopyPath, mode);
|
||||
}
|
||||
|
||||
#ifdef PLATFORM_UNIX
|
||||
#ifdef CPP_UTILITIES_HAS_EXEC_APP
|
||||
/*!
|
||||
* \brief Convenience function which executes the application to be tested with the specified \a args.
|
||||
* \remarks A TestApplication must be present.
|
||||
|
@ -195,7 +199,7 @@ CPP_UTILITIES_EXPORT int execHelperApp(
|
|||
const char *appPath, const char *const *args, std::string &output, std::string &errors, bool suppressLogging = false, int timeout = -1);
|
||||
CPP_UTILITIES_EXPORT int execHelperAppInSearchPath(
|
||||
const char *appName, const char *const *args, std::string &output, std::string &errors, bool suppressLogging = false, int timeout = -1);
|
||||
#endif // PLATFORM_UNIX
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief Allows printing std::optional objects so those can be asserted using CPPUNIT_ASSERT_EQUAL.
|
||||
|
@ -287,6 +291,16 @@ template <typename T, Traits::DisableIf<std::is_integral<T>> * = nullptr> const
|
|||
*
|
||||
* \remarks Requires cppunit.
|
||||
*/
|
||||
#ifdef CPP_UTILITIES_BOOST_PROCESS
|
||||
#define TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, expectedExitStatus) \
|
||||
{ \
|
||||
const auto status = execApp(args, stdout, stderr); \
|
||||
if (status != expectedExitStatus) { \
|
||||
CPPUNIT_FAIL(::CppUtilities::argsToString( \
|
||||
"app exited with status ", status, " (expected ", expectedExitStatus, ")\nstdout: ", stdout, "\nstderr: ", stderr)); \
|
||||
} \
|
||||
}
|
||||
#else
|
||||
#define TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, expectedExitStatus) \
|
||||
{ \
|
||||
const auto status = execApp(args, stdout, stderr); \
|
||||
|
@ -298,6 +312,7 @@ template <typename T, Traits::DisableIf<std::is_integral<T>> * = nullptr> const
|
|||
"app exited with status ", exitStatus, " (expected ", expectedExitStatus, ")\nstdout: ", stdout, "\nstderr: ", stderr)); \
|
||||
} \
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief Asserts whether the specified \a string matches the specified \a regex.
|
||||
|
|
Loading…
Reference in New Issue