Support `execApp()` test helper under Windows as well via Boost.Process

This commit is contained in:
Martchus 2023-08-18 22:57:54 +02:00
parent 18d92fee40
commit af200403de
4 changed files with 98 additions and 7 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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.

View File

@ -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.