2
0
mirror of https://github.com/boostorg/process.git synced 2026-01-20 16:52:14 +00:00

Compare commits

...

50 Commits

Author SHA1 Message Date
Klemens Morgenstern
8e383bf726 environment::native_handle fix
closes #332
2023-08-08 08:38:00 +08:00
Klemens Morgenstern
bfb1ebb5bd Merge branch 'master' into develop
# Conflicts:
#	.github/workflows/ci.yml
#	include/boost/process/environment.hpp
#	include/boost/process/v2/exit_code.hpp
#	include/boost/process/v2/ext/cmd.hpp
#	include/boost/process/v2/ext/cwd.hpp
#	include/boost/process/v2/ext/env.hpp
#	include/boost/process/v2/ext/exe.hpp
#	test/v2/pid.cpp
#	test/v2/process.cpp
2023-06-28 20:30:30 +08:00
Klemens Morgenstern
c005adc8fc simplified stdio. 2023-06-28 20:24:12 +08:00
Klemens Morgenstern
5cab462710 added empty env var check to tests. 2023-06-28 20:24:12 +08:00
Klemens Morgenstern
ccd46dc692 added Bcrypt to gcc win builds. 2023-06-28 20:24:12 +08:00
Klemens Morgenstern
b3c8c3a8da fixed popen test. 2023-06-28 20:24:12 +08:00
Klemens Morgenstern
4dd6f28094 removed code_as_error. 2023-06-28 20:24:12 +08:00
Klemens Morgenstern
d73f228469 popen test adjustements. 2023-06-28 20:24:12 +08:00
Klemens Morgenstern
ccd1717588 test updates for CI 2023-06-28 20:24:12 +08:00
Klemens Morgenstern
9a4aeab97e simplified terminate test. 2023-06-28 20:24:12 +08:00
Klemens Morgenstern
d66dce11bd ext/*.hpp order fix 2023-06-28 20:24:12 +08:00
Klemens Morgenstern
fc38699a4b terminate test logs ec. 2023-06-28 20:24:12 +08:00
Klemens Morgenstern
a859c5151c removed posix-cmake-subdir from github actions. 2023-06-28 20:24:12 +08:00
Klemens Morgenstern
8c2f403841 posix::basic_cmd handles empty cmd
closes #304.
2023-06-28 20:24:12 +08:00
Klemens Morgenstern
6fb2702a79 fixed unsigned & signed warnings
closes #301
2023-06-28 20:24:12 +08:00
Klemens Morgenstern
6cd4244f05 doc typo fixes 2023-06-28 20:24:12 +08:00
Klemens Morgenstern
0c42a58eac ~pipe_buf catches exceptions from overflow
Closes #111
2023-06-28 20:24:12 +08:00
Klemens Morgenstern
f269236d38 vfork launcher fixes
closes #314
2023-06-28 20:24:12 +08:00
Klemens Morgenstern
4b413d34f4 Remove initializer_list<wstring_view> constructors.
They are causing ambiguity and are not that useful.

Closes #313
2023-06-28 20:24:12 +08:00
Klemens Morgenstern
1403af769b process uses v2::detail::throw_error
closes #318
2023-06-28 20:24:12 +08:00
Klemens Morgenstern
964f6d3f7e pipe_* includes error_code
closes #316
2023-06-28 20:24:12 +08:00
Klemens Morgenstern
bccf42a3ec include fix for v2/environment.hpp. 2023-06-28 20:24:12 +08:00
nikola-sh
70c7ae694f Fix MSVC compile errors 2023-06-13 20:44:03 +08:00
Klemens Morgenstern
3211afda4a More typo fixes. 2023-02-22 13:04:13 +08:00
Klemens Morgenstern
d3f006acd4 Typo fixes. 2023-02-22 13:01:46 +08:00
Klemens Morgenstern
99633a6e42 handle_info include fix for handles.hpp. 2023-02-22 12:26:31 +08:00
Klemens Morgenstern
1e614ee43e Reduced amount of test & fixed path comparisons. 2023-02-22 00:54:17 +08:00
Klemens Morgenstern
0e3358705d pid_test fixes for osx. 2023-02-21 16:27:40 +08:00
Klemens Morgenstern
27a35f452d Updated pid test. 2023-02-21 16:27:40 +08:00
Klemens Morgenstern
8fff7283ed removed noexcept from env v2 2023-02-21 16:27:40 +08:00
Klemens Morgenstern
611dac143f sighchld service & test fixes. 2023-02-20 14:09:38 +08:00
Klemens Morgenstern
8d93576b94 close #296. 2023-02-20 14:09:38 +08:00
Klemens Morgenstern
f703845011 ec use locations. 2023-02-20 14:09:38 +08:00
Klemens Morgenstern
abd052e09f xproc fixes 2023-02-20 14:09:38 +08:00
Samuel Venable
a3304564c6 extern process management. 2023-02-20 14:09:37 +08:00
Klemens Morgenstern
5865c6b449 switched to BOOST_DEPRECATED. 2023-02-20 14:09:37 +08:00
Klemens Morgenstern
4c872c0a0d added clang 3.8. noexcept deduction. 2023-02-20 14:09:37 +08:00
Klemens Morgenstern
feabbee098 disabled terminate test for freebsd. 2023-02-20 14:09:37 +08:00
Klemens Morgenstern
a5b6e70c39 [drone] Removed mlocate dep. 2023-02-20 14:09:37 +08:00
Ivan Efimov
347fc68476 Fix string construction in native_environment_impl::get 2023-02-20 14:09:37 +08:00
Klemens Morgenstern
61ff12c8da Deprecated wait_for & wait_until. 2023-02-20 14:09:37 +08:00
Klemens Morgenstern
bd8e81153c Switched vector in list of sigclhd_service.
Closes #175
2023-02-20 14:09:37 +08:00
Gary Miguel
5fde6bec9f fix error message 2023-02-20 14:09:37 +08:00
Klemens
cf64f7dc6a ec fix for search_path with std::filesystem.
closes #287.
2023-02-20 14:09:37 +08:00
Orgad Shaneh
8355c3e1b6 Fix crash on search_path on Windows when PATHEXT is not found 2023-02-20 14:09:37 +08:00
Klemens Morgenstern
de797e388d Update process.cpp 2023-02-20 14:09:37 +08:00
Klemens Morgenstern
fc33435f8b Disabled some tests for freebsd & added interrupt handling to osx test. 2023-02-20 14:09:37 +08:00
sdarwin
f45ec624db Update metadata 2023-02-20 14:09:37 +08:00
Klemens Morgenstern
9682056278 Typo fix. 2023-02-20 14:09:37 +08:00
Klemens Morgenstern
a00115b454 Include fixes. 2023-02-20 14:09:37 +08:00
39 changed files with 192 additions and 375 deletions

View File

@@ -199,52 +199,3 @@ jobs:
run: |
cd ../boost-root
b2 -j3 libs/%LIBRARY%/test toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} address-model=${{matrix.addrmd}} variant=debug,release embed-manifest-via=linker
posix-cmake-subdir:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-18.04
- os: ubuntu-20.04
- os: ubuntu-22.04
- os: macos-11
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v2
- name: Install packages
if: matrix.install
run: sudo apt install ${{matrix.install}}
- name: Setup Boost
run: |
echo GITHUB_REPOSITORY: $GITHUB_REPOSITORY
LIBRARY=${GITHUB_REPOSITORY#*/}
echo LIBRARY: $LIBRARY
echo "LIBRARY=$LIBRARY" >> $GITHUB_ENV
echo GITHUB_BASE_REF: $GITHUB_BASE_REF
echo GITHUB_REF: $GITHUB_REF
REF=${GITHUB_BASE_REF:-$GITHUB_REF}
REF=${REF#refs/heads/}
echo REF: $REF
BOOST_BRANCH=develop && [ "$REF" == "master" ] && BOOST_BRANCH=master || true
echo BOOST_BRANCH: $BOOST_BRANCH
cd ..
git clone -b $BOOST_BRANCH --depth 1 https://github.com/boostorg/boost.git boost-root
cd boost-root
cp -r $GITHUB_WORKSPACE/* libs/$LIBRARY
git submodule update --init tools/boostdep
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY
- name: Use library with add_subdirectory
run: |
cd ../boost-root/libs/$LIBRARY/test/cmake_subdir_test
mkdir __build__ && cd __build__
cmake ..
cmake --build .
ctest --output-on-failure --no-tests=error

View File

@@ -35,7 +35,7 @@ In this library the following functions are used for the creation of unnamed pip
As the name suggests, named pipes have a string identifier. This means that a
handle to them can be obtained with the identifier, too.
The implementation on posix uses [@(http://pubs.opengroup.org/onlinepubs/009695399/functions/mkfifo.html fifos],
The implementation on posix uses [@http://pubs.opengroup.org/onlinepubs/009695399/functions/mkfifo.html fifos],
which means, that the named pipe behaves like a file.
Windows does provide a facility called [@https://msdn.microsoft.com/en-us/library/windows/desktop/aa365150(v=vs.85).aspx named pipes],

View File

@@ -57,7 +57,7 @@ file-handles for the subprocess.
Certain parts of boost.process were not as reliable as they should've been.
This concerns especially the `wait_for`` and `wait_until` functions on the process.
This concerns especially the `wait_for` and `wait_until` functions on the process.
The latter are easy to do on windows, but posix does not provide an API for this.
Thus the wait_for used signals or fork, which was all but safe.
Since process v2 is based on asio and thus supports cancellation,

View File

@@ -112,7 +112,10 @@ struct exe_cmd_init<char> : boost::process::detail::api::handler_base_ext
{
if (exe.empty()) //cmd style
{
exec.exe = args.front().c_str();
if (args.empty())
exec.exe = "";
else
exec.exe = args.front().c_str();
exec.cmd_style = true;
}
else

View File

@@ -77,12 +77,9 @@ public:
void assign_source(native_handle_type h) { _source = h;}
void assign_sink (native_handle_type h) { _sink = h;}
int_type write(const char_type * data, int_type count)
{
int_type write_len;
ssize_t write_len;
while ((write_len = ::write(_sink, data, count * sizeof(char_type))) == -1)
{
//Try again if interrupted
@@ -90,19 +87,19 @@ public:
if (err != EINTR)
::boost::process::detail::throw_last_error();
}
return write_len;
return static_cast<int_type>(write_len);
}
int_type read(char_type * data, int_type count)
{
int_type read_len;
while ((read_len = static_cast<int_type>(::read(_source, data, count * sizeof(char_type)))) == -1)
ssize_t read_len;
while ((read_len = ::read(_source, data, count * sizeof(char_type))) == -1)
{
//Try again if interrupted
auto err = errno;
if (err != EINTR)
::boost::process::detail::throw_last_error();
}
return read_len;
return static_cast<int_type>(read_len);
}
bool is_open() const

View File

@@ -210,7 +210,7 @@ public:
Char ** _env_impl = &*_env_arr.data();
native_handle_type native_handle() const {return &_data.front();}
native_handle_type native_handle() const {return _env_impl;}
};

View File

@@ -158,7 +158,7 @@ basic_pipe<Char, Traits>::basic_pipe(const std::string & name)
::boost::process::detail::throw_last_error("create_named_pipe() failed");
::boost::winapi::HANDLE_ sink = boost::winapi::create_file(
name.c_str(),
name_.c_str(),
::boost::winapi::GENERIC_WRITE_, 0, nullptr,
OPEN_EXISTING_,
FILE_FLAG_OVERLAPPED_, //to allow read

View File

@@ -14,6 +14,8 @@
#include <boost/winapi/handles.hpp>
#include <boost/process/detail/used_handles.hpp>
#include <boost/process/detail/handler_base.hpp>
#include <boost/system/error_code.hpp>
namespace boost { namespace process { namespace detail { namespace windows {

View File

@@ -15,11 +15,11 @@
#include <boost/winapi/handles.hpp>
#include <boost/process/detail/used_handles.hpp>
#include <boost/process/detail/handler_base.hpp>
#include <boost/system/error_code.hpp>
namespace boost { namespace process { namespace detail { namespace windows {
template<int p1, int p2>
struct pipe_out : public ::boost::process::detail::handler_base, ::boost::process::detail::uses_handles
{

View File

@@ -263,7 +263,7 @@ public:
auto st1 = key + ::boost::process::detail::equal_sign<Char>();
while (*p != nullptr)
{
const int len = std::char_traits<Char>::length(*p);
const std::ptrdiff_t len = std::char_traits<Char>::length(*p);
if ((std::distance(st1.begin(), st1.end()) < len)
&& std::equal(st1.begin(), st1.end(), *p))
break;
@@ -277,7 +277,7 @@ public:
auto st1 = key + ::boost::process::detail::equal_sign<Char>();
while (*p != nullptr)
{
const int len = std::char_traits<Char>::length(*p);
const std::size_t len = std::char_traits<Char>::length(*p);
if ((std::distance(st1.begin(), st1.end()) < len)
&& std::equal(st1.begin(), st1.end(), *p))
break;
@@ -292,8 +292,9 @@ public:
auto st1 = st + ::boost::process::detail::equal_sign<Char>();
while (*p != nullptr)
{
const int len = std::char_traits<Char>::length(*p);
if ((std::distance(st1.begin(), st1.end()) < len)
const std::size_t len = std::char_traits<Char>::length(*p);
if ((std::distance(st1.begin(), st1.end()) <
static_cast<typename string_type::iterator::difference_type>(len))
&& std::equal(st1.begin(), st1.end(), *p))
return 1u;
p++;

View File

@@ -122,10 +122,14 @@ struct basic_pipebuf : std::basic_streambuf<CharT, Traits>
///Destructor -> writes the frest of the data
~basic_pipebuf()
try
{
if (basic_pipebuf::is_open())
basic_pipebuf::overflow(Traits::eof());
}
catch (process_error & )
{
}
///Move construct from a pipe.
basic_pipebuf(pipe_type && p) : _pipe(std::move(p)),

View File

@@ -13,6 +13,7 @@
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/cstring_ref.hpp>
#include <boost/process/v2/detail/utf8.hpp>
#include <boost/type_traits.hpp>
#include <functional>
#include <memory>
#include <numeric>
@@ -501,8 +502,8 @@ struct key
template< class Source >
key( const Source& source,
decltype(source.data()) = nullptr,
decltype(source.size()) = 0u)
decltype(std::declval<Source>().data()) = nullptr,
decltype(std::declval<Source>().size()) = 0u)
: value_(
BOOST_PROCESS_V2_NAMESPACE::detail::conv_string<char_type, traits_type>(
source.data(), source.size()))
@@ -724,8 +725,8 @@ struct value
template< class Source >
value( const Source& source,
decltype(source.data()) = nullptr,
decltype(source.size()) = 0u)
decltype(std::declval<Source>().data()) = nullptr,
decltype(std::declval<Source>().size()) = 0u)
: value_(BOOST_PROCESS_V2_NAMESPACE::detail::conv_string<char_type, traits_type>(
source.data(), source.size()))
{
@@ -974,8 +975,8 @@ struct key_value_pair
template< class Source >
key_value_pair( const Source& source,
decltype(source.data()) = nullptr,
decltype(source.size()) = 0u)
decltype(std::declval<Source>().data()) = nullptr,
decltype(std::declval<Source>().size()) = 0u)
: value_(BOOST_PROCESS_V2_NAMESPACE::detail::conv_string<char_type, traits_type>(
source.data(), source.size()))
{
@@ -1743,8 +1744,8 @@ struct process_environment
return build_env(env_buffer);
}
process_environment(std::initializer_list<string_view> sv) : unicode_env{build_env(sv, "")} {}
process_environment(std::initializer_list<wstring_view> sv) : unicode_env{build_env(sv, L"")} {}
process_environment(std::initializer_list<string_view> sv) : unicode_env{build_env(sv)} {}
process_environment(std::initializer_list<wstring_view> sv) : unicode_env{build_env(sv)} {}
template<typename Args>
process_environment(Args && args) : unicode_env{build_env(std::forward<Args>(args))}

View File

@@ -94,12 +94,19 @@ inline int evaluate_exit_code(int code)
#endif
/** Convert the exit-code in a completion into an error if the actual error isn't set.
/// @{
/** Helper to subsume an exit-code into an error_code if there's no actual error isn't set.
* @code {.cpp}
* process proc{ctx, "exit", {"1"}};
*
* proc.async_wait(code_as_error(
* proc.async_wait(
* asio::deferred(
* [&proc](error_code ec, int)
* {
* return asio::deferred.values(
* check_exit_code(ec, proc.native_exit_code())
* );
*
* [](error_code ec)
* {
* assert(ec.value() == 10);
@@ -107,144 +114,19 @@ inline int evaluate_exit_code(int code)
* }));
*
* @endcode
*/
template<typename CompletionToken>
struct code_as_error_t
*/
inline error_code check_exit_code(
error_code &ec, native_exit_code_type native_code,
const error_category & category = error::get_exit_code_category())
{
CompletionToken token_;
const error_category & category;
template<typename Token_>
code_as_error_t(Token_ && token, const error_category & category)
: token_(std::forward<Token_>(token)), category(category)
{
}
};
/// Deduction function for code_as_error_t.
template<typename CompletionToken>
code_as_error_t<CompletionToken> code_as_error(
CompletionToken && token,
const error_category & category = error::get_exit_code_category())
{
return code_as_error_t<typename std::decay<CompletionToken>::type>(
std::forward<CompletionToken>(token), category);
};
namespace detail
{
template<typename Handler>
struct code_as_error_handler
{
typedef void result_type;
template<typename H>
code_as_error_handler(H && h, const error_category & category)
: handler_(std::forward<H>(h)), category(category)
{
}
void operator()(error_code ec, native_exit_code_type code)
{
if (!ec)
BOOST_PROCESS_V2_ASSIGN_EC(ec, code, category)
std::move(handler_)(ec);
}
Handler handler_;
const error_category & category;
};
if (!ec)
BOOST_PROCESS_V2_ASSIGN_EC(ec, native_code, category);
return ec;
}
/// @}
BOOST_PROCESS_V2_END_NAMESPACE
#if !defined(BOOST_PROCESS_V2_STANDALONE)
namespace boost
{
#endif
namespace asio
{
template <typename CompletionToken>
struct async_result<
BOOST_PROCESS_V2_NAMESPACE::code_as_error_t<CompletionToken>,
void(BOOST_PROCESS_V2_NAMESPACE::error_code,
BOOST_PROCESS_V2_NAMESPACE::native_exit_code_type)>
{
using signature = void(BOOST_PROCESS_V2_NAMESPACE::error_code);
using return_type = typename async_result<CompletionToken, void(BOOST_PROCESS_V2_NAMESPACE::error_code)>::return_type;
template <typename Initiation>
struct init_wrapper
{
init_wrapper(Initiation init)
: initiation_(std::move(init))
{
}
template <typename Handler, typename... Args>
void operator()(
Handler && handler,
const BOOST_PROCESS_V2_NAMESPACE::error_category & cat,
Args && ... args)
{
std::move(initiation_)(
BOOST_PROCESS_V2_NAMESPACE::detail::code_as_error_handler<typename decay<Handler>::type>(
std::forward<Handler>(handler), cat),
std::forward<Args>(args)...);
}
Initiation initiation_;
};
template <typename Initiation, typename RawCompletionToken, typename... Args>
static BOOST_PROCESS_V2_INITFN_DEDUCED_RESULT_TYPE(CompletionToken, signature,
(async_initiate<CompletionToken, signature>(
declval<init_wrapper<typename decay<Initiation>::type> >(),
declval<CompletionToken&>(),
declval<BOOST_ASIO_MOVE_ARG(Args)>()...)))
initiate(
Initiation && initiation,
RawCompletionToken && token,
Args &&... args)
{
return async_initiate<CompletionToken, signature>(
init_wrapper<typename decay<Initiation>::type>(
std::forward<Initiation>(initiation)),
token.token_,
token.category,
std::forward<Args>(args)...);
}
};
template<template <typename, typename> class Associator, typename Handler, typename DefaultCandidate>
struct associator<Associator,
BOOST_PROCESS_V2_NAMESPACE::detail::code_as_error_handler<Handler>, DefaultCandidate>
: Associator<Handler, DefaultCandidate>
{
static typename Associator<Handler, DefaultCandidate>::type get(
const BOOST_PROCESS_V2_NAMESPACE::detail::code_as_error_handler<Handler> & h,
const DefaultCandidate& c = DefaultCandidate()) noexcept
{
return Associator<Handler, DefaultCandidate>::get(h.handler_, c);
}
};
}
#if !defined(BOOST_PROCESS_V2_STANDALONE)
} // boost
#endif
#endif //BOOST_PROCESS_V2_EXIT_CODE_HPP

View File

@@ -20,6 +20,11 @@ BOOST_PROCESS_V2_BEGIN_NAMESPACE
namespace ext {
#if defined(BOOST_PROCESS_V2_WINDOWS)
BOOST_PROCESS_V2_DECL shell cmd(HANDLE handle, error_code & ec);
BOOST_PROCESS_V2_DECL shell cmd(HANDLE handle);
#endif
/// @{
/// Get the argument vector of another process
BOOST_PROCESS_V2_DECL shell cmd(pid_type pid, error_code & ec);

View File

@@ -15,6 +15,11 @@ BOOST_PROCESS_V2_BEGIN_NAMESPACE
namespace ext {
#if defined(BOOST_PROCESS_V2_WINDOWS)
BOOST_PROCESS_V2_DECL filesystem::path cwd(HANDLE handle, error_code & ec);
BOOST_PROCESS_V2_DECL filesystem::path cwd(HANDLE handle);
#endif
/// @{
/// Obtain the current path of another process
BOOST_PROCESS_V2_DECL filesystem::path cwd(pid_type pid, error_code & ec);
@@ -42,12 +47,6 @@ BOOST_PROCESS_V2_DECL filesystem::path cwd(basic_process_handle<Executor> & hand
/// @}
#if defined(BOOST_PROCESS_V2_WINDOWS)
BOOST_PROCESS_V2_DECL filesystem::path cwd(HANDLE handle, error_code & ec);
BOOST_PROCESS_V2_DECL filesystem::path cwd(HANDLE handle);
#endif
} // namespace ext
BOOST_PROCESS_V2_END_NAMESPACE

View File

@@ -109,6 +109,11 @@ struct env_view
detail::ext::native_env_handle_deleter> handle_;
};
#if defined(BOOST_PROCESS_V2_WINDOWS)
BOOST_PROCESS_V2_DECL env_view env(HANDLE handle, error_code & ec);
BOOST_PROCESS_V2_DECL env_view env(HANDLE handle);
#endif
/// @{
/// Get the environment of another process.
BOOST_PROCESS_V2_DECL env_view env(pid_type pid, error_code & ec);
@@ -136,10 +141,6 @@ BOOST_PROCESS_V2_DECL env_view env(basic_process_handle<Executor> & handle)
/// @}
#if defined(BOOST_PROCESS_V2_WINDOWS)
BOOST_PROCESS_V2_DECL env_view env(HANDLE handle, error_code & ec);
BOOST_PROCESS_V2_DECL env_view env(HANDLE handle);
#endif
} // namespace ext

View File

@@ -16,6 +16,11 @@ BOOST_PROCESS_V2_BEGIN_NAMESPACE
namespace ext {
#if defined(BOOST_PROCESS_V2_WINDOWS)
BOOST_PROCESS_V2_DECL filesystem::path exe(HANDLE handle, error_code & ec);
BOOST_PROCESS_V2_DECL filesystem::path exe(HANDLE handle);
#endif
/// @{
/// Return the executable of another process by pid or handle.
BOOST_PROCESS_V2_DECL filesystem::path exe(pid_type pid, error_code & ec);
@@ -45,11 +50,6 @@ filesystem::path exe(basic_process_handle<Executor> & handle)
///@}
#if defined(BOOST_PROCESS_V2_WINDOWS)
BOOST_PROCESS_V2_DECL filesystem::path exe(HANDLE handle, error_code & ec);
BOOST_PROCESS_V2_DECL filesystem::path exe(HANDLE handle);
#endif
} // namespace ext
BOOST_PROCESS_V2_END_NAMESPACE

View File

@@ -55,7 +55,7 @@ struct exit_code_category final : public error_category
}
std::string message(int status) const
{
switch (status)
switch (evaluate_exit_code(status))
{
case v2::detail::still_active:
return "still-active";

View File

@@ -109,42 +109,6 @@ struct basic_popen : basic_process<Executor>
));
}
/// Construct a child from a property list and launch it using the default process launcher.
template<typename ... Inits>
explicit basic_popen(
executor_type executor,
const filesystem::path& exe,
std::initializer_list<wstring_view> args,
Inits&&... inits)
: basic_process<Executor>(executor)
{
this->basic_process<Executor>::operator=(
default_process_launcher()(
this->get_executor(), exe, args,
std::forward<Inits>(inits)...,
process_stdio{stdin_, stdout_}
));
}
/// Construct a child from a property list and launch it using the default process launcher.
template<typename Launcher, typename ... Inits>
explicit basic_popen(
Launcher && launcher,
executor_type executor,
const filesystem::path& exe,
std::initializer_list<wstring_view> args,
Inits&&... inits)
: basic_process<Executor>(executor)
{
this->basic_process<Executor>::operator=(
std::forward<Launcher>(launcher)(
this->get_executor(), exe, args,
std::forward<Inits>(inits)...,
process_stdio{stdin_, stdout_}
));
}
/// Construct a child from a property list and launch it using the default process launcher.
template<typename Args, typename ... Inits>
explicit basic_popen(

View File

@@ -8,6 +8,7 @@
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/cstring_ref.hpp>
#include <boost/process/v2/posix/detail/close_handles.hpp>
#include <boost/process/v2/detail/throw_error.hpp>
#include <boost/process/v2/detail/utf8.hpp>
#if defined(BOOST_PROCESS_V2_STANDALONE)
@@ -313,7 +314,7 @@ struct default_launcher
auto proc = (*this)(context, ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "default_launcher");
v2::detail::throw_error(ec, "default_launcher");
return proc;
}
@@ -344,7 +345,7 @@ struct default_launcher
auto proc = (*this)(std::move(exec), ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "default_launcher");
v2::detail::throw_error(ec, "default_launcher");
return proc;
}

View File

@@ -29,7 +29,7 @@ struct fork_and_forget_launcher : default_launcher
auto proc = (*this)(context, ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "fork_and_forget_launcher");
v2::detail::throw_error(ec, "fork_and_forget_launcher");
return proc;
}
@@ -60,7 +60,7 @@ struct fork_and_forget_launcher : default_launcher
auto proc = (*this)(std::move(exec), ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "fork_and_forget_launcher");
v2::detail::throw_error(ec, "fork_and_forget_launcher");
return proc;
}

View File

@@ -34,7 +34,7 @@ struct pdfork_launcher : default_launcher
auto proc = (*this)(context, ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "pdfork_launcher");
v2::detail::throw_error(ec, "pdfork_launcher");
return proc;
}
@@ -65,7 +65,7 @@ struct pdfork_launcher : default_launcher
auto proc = (*this)(std::move(exec), ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "pdfork_launcher");
v2::detail::throw_error(ec, "pdfork_launcher");
return proc;
}

View File

@@ -31,7 +31,7 @@ struct vfork_launcher : default_launcher
auto proc = (*this)(context, ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "default_launcher");
v2::detail::throw_error(ec, "default_launcher");
return proc;
}
@@ -62,7 +62,7 @@ struct vfork_launcher : default_launcher
auto proc = (*this)(std::move(exec), ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "default_launcher");
v2::detail::throw_error(ec, "default_launcher");
return proc;
}
@@ -101,7 +101,6 @@ struct vfork_launcher : default_launcher
}
else if (pid == 0)
{
ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_child);
ec = detail::on_exec_setup(*this, executable, argv, inits...);
if (!ec)
close_all_fds(ec);
@@ -110,7 +109,7 @@ struct vfork_launcher : default_launcher
BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category())
detail::on_exec_error(*this, executable, argv, ec, inits...);
::exit(EXIT_FAILURE);
::_exit(EXIT_FAILURE);
return basic_process<Executor>{exec};
}
ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent);

View File

@@ -93,17 +93,7 @@ struct basic_process
: basic_process(default_process_launcher()(std::move(executor), exe, args, std::forward<Inits>(inits)...))
{
}
/// Construct a child from a property list and launch it using the default launcher..
template<typename ... Inits>
explicit basic_process(
executor_type executor,
const filesystem::path& exe,
std::initializer_list<wstring_view> args,
Inits&&... inits)
: basic_process(default_process_launcher()(std::move(executor), exe, args, std::forward<Inits>(inits)...))
{
}
/// Construct a child from a property list and launch it using the default launcher..
template<typename Args, typename ... Inits>
explicit basic_process(
@@ -265,7 +255,7 @@ struct basic_process
{
error_code ec;
if (running(ec))
wait(ec);
process_handle_.wait(exit_status_, ec);
if (ec)
detail::throw_error(ec, "wait failed");
return exit_code();
@@ -287,8 +277,9 @@ struct basic_process
return boost::exchange(process_handle_, get_executor());
#endif
}
// Get the native
/// Get the native
native_handle_type native_handle() {return process_handle_.native_handle(); }
/// Return the evaluated exit_code.
int exit_code() const
{
return evaluate_exit_code(exit_status_);
@@ -334,7 +325,7 @@ struct basic_process
/** Note that this might be a process that already exited.*/
bool is_open() const { return process_handle_.is_open(); }
/// Asynchronously wait for the process to exit and deliver the portable exit-code in the completion handler.
/// Asynchronously wait for the process to exit and deliver the native exit-code in the completion handler.
template <BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void (error_code, int))
WaitHandler BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, int))

View File

@@ -103,9 +103,15 @@ struct process_io_binding
template<typename Executor>
process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe<Executor> & readable_pipe,
typename std::enable_if<Target != STD_INPUT_HANDLE, Executor*>::type = 0)
process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe<Executor> & pipe)
{
if (Target == STD_INPUT_HANDLE)
{
auto h_ = pipe.native_handle();
h = std::unique_ptr<void, handle_closer>{h_, get_flags(h_)};
return ;
}
BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
error_code ec;
BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
@@ -113,14 +119,19 @@ struct process_io_binding
detail::throw_error(ec, "create_pipe");
h = std::unique_ptr<void, handle_closer>{p[1], true};
readable_pipe.assign(p[0]);
pipe.assign(p[0]);
}
template<typename Executor>
process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe<Executor> & writable_pipe,
typename std::enable_if<Target == STD_INPUT_HANDLE, Executor*>::type = 0)
process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe<Executor> & pipe)
{
if (Target != STD_INPUT_HANDLE)
{
auto h_ = pipe.native_handle();
h = std::unique_ptr<void, handle_closer>{h_, get_flags(h_)};
return ;
}
BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
error_code ec;
BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
@@ -128,7 +139,7 @@ struct process_io_binding
detail::throw_error(ec, "create_pipe");
h = std::unique_ptr<void, handle_closer>{p[0], true};
writable_pipe.assign(p[1]);
pipe.assign(p[1]);
}
};
@@ -170,13 +181,19 @@ struct process_io_binding
}
template<typename Executor>
process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe<Executor> & readable_pipe,
typename std::enable_if<Target != STDIN_FILENO, Executor*>::type = 0)
process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe<Executor> & readable_pipe)
{
if (Target == STDIN_FILENO)
{
fd = readable_pipe.native_handle();
return ;
}
BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
if (ec)
return ;
detail::throw_error(ec, "create_pipe");
fd = p[1];
if (::fcntl(p[0], F_SETFD, FD_CLOEXEC) == -1)
{
@@ -189,13 +206,20 @@ struct process_io_binding
template<typename Executor>
process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe<Executor> & writable_pipe,
typename std::enable_if<Target == STDIN_FILENO, Executor*>::type = 0)
process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe<Executor> & writable_pipe)
{
if (Target != STDIN_FILENO)
{
fd = writable_pipe.native_handle();
return ;
}
BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
error_code ec;
BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
if (ec)
return ;
detail::throw_error(ec, "create_pipe");
fd = p[0];
if (::fcntl(p[1], F_SETFD, FD_CLOEXEC) == -1)
{

View File

@@ -37,7 +37,7 @@ struct as_user_launcher : default_launcher
auto proc = (*this)(context, ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "as_user_launcher");
v2::detail::throw_error(ec, "as_user_launcher");
return proc;
}
@@ -68,7 +68,7 @@ struct as_user_launcher : default_launcher
auto proc = (*this)(std::move(exec), ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "as_user_launcher");
detail::throw_error(ec, "as_user_launcher");
return proc;
}

View File

@@ -14,6 +14,7 @@
#include <boost/process/v2/cstring_ref.hpp>
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/detail/last_error.hpp>
#include <boost/process/v2/detail/throw_error.hpp>
#include <boost/process/v2/detail/utf8.hpp>
#include <boost/process/v2/error.hpp>
@@ -244,7 +245,7 @@ struct default_launcher
auto proc = (*this)(context, ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "default_launcher");
v2::detail::throw_error(ec, "default_launcher");
return proc;
}
@@ -275,7 +276,7 @@ struct default_launcher
auto proc = (*this)(std::move(exec), ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "default_launcher");
detail::throw_error(ec, "default_launcher");
return proc;
}
@@ -311,7 +312,6 @@ struct default_launcher
&startup_info.StartupInfo,
&process_information);
auto ec__ = detail::get_last_error();
if (ok == 0)
{
BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec)

View File

@@ -66,7 +66,7 @@ namespace windows
{
if (wc == L'"')
*(itr++) = L'\\';
*(itr++) = wc;
*(itr++) = wc;
}
*(itr ++) = L'"';

View File

@@ -20,7 +20,7 @@ namespace windows
/// A windows launcher using CreateProcessWithLogon instead of CreateProcess
struct with_logon_launcher : default_launcher
{
std::wstring username, domain, password;
std::wstring username, password, domain;
DWORD logon_flags{0u};
with_logon_launcher(std::wstring username = L"",
@@ -72,7 +72,7 @@ struct with_logon_launcher : default_launcher
auto proc = (*this)(std::move(exec), ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "with_logon_launcher");
v2::detail::throw_error(ec, "with_logon_launcher");
return proc;
}

View File

@@ -38,7 +38,7 @@ struct with_token_launcher : default_launcher
auto proc = (*this)(context, ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "with_token_launcher");
v2::detail::throw_error(ec, "with_token_launcher");
return proc;
}
@@ -69,7 +69,7 @@ struct with_token_launcher : default_launcher
auto proc = (*this)(std::move(exec), ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
asio::detail::throw_error(ec, "with_token_launcher");
detail::throw_error(ec, "with_token_launcher");
return proc;
}

View File

@@ -30,6 +30,7 @@ BOOST_AUTO_TEST_CASE(empty, *boost::unit_test::timeout(5))
BOOST_CHECK(ev.empty());
BOOST_CHECK_EQUAL(ev.size(), 0u);
BOOST_CHECK_EQUAL(ev.end() - ev.begin(), 0);
auto nh = ev.native_handle();
ev["Thingy"] = "My value";
BOOST_CHECK(!ev.empty());

View File

@@ -111,7 +111,7 @@ BOOST_AUTO_TEST_CASE(wait_group_test_timeout, *boost::unit_test::timeout(15))
bp::child c2(
master_test_suite().argv[1],
"--wait", "4",
"--wait", "5",
g,
ec
);

View File

@@ -97,7 +97,8 @@ BOOST_AUTO_TEST_CASE(leak_test, *boost::unit_test::timeout(5))
#if defined( BOOST_WINDOWS_API )
std::thread thr([]{});
BOOST_CHECK(!bt::is_stream_handle(thr.native_handle(), ec)); BOOST_CHECK_MESSAGE(!ec, ec.message());
BOOST_CHECK(!bt::is_stream_handle(::GetCurrentProcess(), ec));
BOOST_CHECK_MESSAGE(!ec, ec.message());
thr.join();
#else
# if defined(TFD_CLOEXEC) //check timer

View File

@@ -23,6 +23,7 @@
#include <iterator>
#include <iostream>
#include <cstdlib>
#include <thread>
#if defined(BOOST_POSIX_API)
# include <boost/lexical_cast.hpp>
# include <boost/iostreams/device/file_descriptor.hpp>
@@ -148,7 +149,8 @@ int main(int argc, char *argv[])
}
else if (vm["loop"].as<bool>())
{
while (true);
while (true)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
else if (vm["abort"].as<bool>())
{

View File

@@ -24,15 +24,6 @@ namespace bp = boost::process;
BOOST_AUTO_TEST_CASE(terminate_set_on_error, *boost::unit_test::timeout(5))
{
std::atomic<bool> done{false};
std::thread thr{
[&]
{
for (int i = 0; i < 50 && !done.load(); i++)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
BOOST_REQUIRE(done.load());
}};
using boost::unit_test::framework::master_test_suite;
std::error_code ec;
bp::child c(
@@ -44,31 +35,14 @@ BOOST_AUTO_TEST_CASE(terminate_set_on_error, *boost::unit_test::timeout(5))
BOOST_CHECK(c.valid());
BOOST_CHECK(c.running(ec));
BOOST_CHECK(!c.wait_for(std::chrono::milliseconds(100), ec));
BOOST_CHECK(c.running(ec));
BOOST_CHECK(c.valid());
c.terminate(ec);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::this_thread::sleep_for(std::chrono::milliseconds(25));
BOOST_CHECK(!c.running(ec));
BOOST_CHECK(!ec);
done.store(true);
thr.join();
}
BOOST_AUTO_TEST_CASE(terminate_throw_on_error, *boost::unit_test::timeout(5))
{
std::atomic<bool> done{false};
std::thread thr{
[&]
{
for (int i = 0; i < 50 && !done.load(); i++)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
BOOST_REQUIRE(done.load());
}};
using boost::unit_test::framework::master_test_suite;
std::error_code ec;
@@ -79,17 +53,10 @@ BOOST_AUTO_TEST_CASE(terminate_throw_on_error, *boost::unit_test::timeout(5))
ec
);
BOOST_REQUIRE(!ec);
BOOST_CHECK(c.valid());
BOOST_CHECK(c.running());
BOOST_CHECK(!c.wait_for(std::chrono::milliseconds(100), ec));
BOOST_CHECK(c.running(ec));
BOOST_CHECK(c.valid());
c.terminate();
std::this_thread::sleep_for(std::chrono::milliseconds(5));
std::this_thread::sleep_for(std::chrono::milliseconds(25));
BOOST_CHECK(!c.running());
done.store(true);
thr.join();
}

View File

@@ -13,6 +13,7 @@ if [ os.name ] = NT
lib Advapi32 ;
lib Ntdll ;
lib user32 ;
lib Bcrypt ;
}
project : requirements
@@ -29,6 +30,7 @@ project : requirements
<target-os>bsd:<linkflags>-lkvm
<os>NT,<toolset>cw:<library>ws2_32
<os>NT,<toolset>gcc:<library>ws2_32
<os>NT,<toolset>gcc:<library>Bcrypt
<define>BOOST_PROCESS_V2_SEPARATE_COMPILATION=1
;

View File

@@ -62,7 +62,8 @@ BOOST_AUTO_TEST_CASE(environment)
ec.clear();
for (auto && ke : bpe::current())
BOOST_CHECK_EQUAL(bpe::get(std::get<0>(ke)), std::get<1>(ke));
if (!std::get<1>(ke).empty())
BOOST_CHECK_EQUAL(bpe::get(std::get<0>(ke)), std::get<1>(ke));
#if defined(BOOST_PROCESS_V2_POSIX)
@@ -150,7 +151,11 @@ BOOST_AUTO_TEST_CASE(wenvironment)
BOOST_CHECK(ec);
for (const auto ke : bpe::current())
{
std::string key = std::get<0>(ke).string();
if (!std::get<1>(ke).empty())
BOOST_CHECK_EQUAL(bpe::get(std::get<0>(ke)), std::get<1>(ke));
}
#if defined(BOOST_PROCESS_V2_WINDOWS)
BOOST_CHECK_EQUAL(bpe::key(L"FOO"), bpe::key_view(L"Foo"));

View File

@@ -24,8 +24,6 @@ BOOST_AUTO_TEST_CASE(test_pid)
BOOST_CHECK_GT(all.size(), 0u);
BOOST_CHECK(itr != all.end());
}
BOOST_AUTO_TEST_CASE(child_pid)
@@ -34,12 +32,12 @@ BOOST_AUTO_TEST_CASE(child_pid)
using boost::unit_test::framework::master_test_suite;
const auto pth = bp2::filesystem::absolute(master_test_suite().argv[1]);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto cs = bp2::child_pids(bp2::current_pid());
boost::asio::io_context ctx;
bp2::process proc(ctx, pth, {"sleep", "50000"});
std::this_thread::sleep_for(std::chrono::milliseconds(20));
bp2::process proc(ctx, pth, {"loop"});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto c2 = bp2::child_pids(bp2::current_pid());
BOOST_CHECK_LT(cs.size(), c2.size());
BOOST_CHECK(std::find(cs.begin(), cs.end(), proc.id()) == cs.end());

View File

@@ -167,7 +167,7 @@ BOOST_AUTO_TEST_CASE(request_exit)
asio::writable_pipe wp{ctx};
asio::connect_pipe(rp, wp);
bpv::process proc(ctx, sh, {}, bpv::process_stdio{wp}
bpv::process proc(ctx, sh, {}, bpv::process_stdio{rp}
#if defined(ASIO_WINDOWS)
, asio::windows::show_window_minimized_not_active
#endif
@@ -371,27 +371,26 @@ BOOST_AUTO_TEST_CASE(popen)
asio::io_context ctx;
asio::readable_pipe rp{ctx};
// default CWD
bpv::popen proc(/*bpv::default_process_launcher(), */ctx, pth, {"echo"});
asio::write(proc, asio::buffer("FOOBAR"));
BOOST_CHECK_EQUAL(asio::write(proc, asio::buffer("FOOBAR", 6)), 6);
proc.get_stdin().close();
std::string res;
boost::system::error_code ec;
std::size_t n = asio::read(proc, asio::dynamic_buffer(res), ec);
while (ec == asio::error::interrupted)
n += asio::read(rp, asio::dynamic_buffer(res), ec);
n += asio::read(proc, asio::dynamic_buffer(res), ec);
BOOST_CHECK_MESSAGE(ec == asio::error::eof || ec == asio::error::broken_pipe, ec.message());
BOOST_CHECK_MESSAGE(ec == asio::error::eof
|| ec == asio::error::broken_pipe,
ec.message());
BOOST_REQUIRE_GE(n, 1u);
// remove EOF
res.pop_back();
BOOST_CHECK_EQUAL(res, "FOOBAR");
proc.get_stdin().close();
proc.wait();
BOOST_CHECK_MESSAGE(proc.exit_code() == 0, proc.exit_code());
}
@@ -520,13 +519,34 @@ BOOST_AUTO_TEST_CASE(exit_code_as_error)
proc3.terminate();
proc1.async_wait(bpv::code_as_error([&](bpv::error_code ec){called ++; BOOST_CHECK(!ec);}));
proc2.async_wait(bpv::code_as_error([&](bpv::error_code ec){called ++; BOOST_CHECK_MESSAGE(ec, ec.message());}));
proc3.async_wait(bpv::code_as_error([&](bpv::error_code ec){called ++; BOOST_CHECK_MESSAGE(ec, ec.message());}));
proc1.async_wait(
[&](bpv::error_code ec, int)
{
called ++;
bpv::check_exit_code(ec, proc1.native_exit_code());
BOOST_CHECK(!ec);
});
proc2.async_wait(
[&](bpv::error_code ec, int)
{
called ++;
bpv::check_exit_code(ec, proc2.native_exit_code());
BOOST_CHECK_MESSAGE(ec, ec.message());
});
proc3.async_wait(
[&](bpv::error_code ec, int)
{
called ++;
bpv::check_exit_code(ec, proc3.native_exit_code());
BOOST_CHECK_MESSAGE(ec, ec.message());
});
ctx.run();
BOOST_CHECK_EQUAL(called, 3);
}
BOOST_AUTO_TEST_CASE(bind_launcher)
@@ -537,17 +557,13 @@ BOOST_AUTO_TEST_CASE(bind_launcher)
asio::io_context ctx;
asio::readable_pipe rp{ctx};
asio::writable_pipe wp{ctx};
asio::connect_pipe(rp, wp);
auto target = bpv::filesystem::canonical(bpv::filesystem::temp_directory_path());
auto l = bpv::bind_default_launcher(bpv::process_start_dir(target));
std::vector<std::string> args = {"print-cwd"};
// default CWD
bpv::process proc = l(ctx, pth, args, bpv::process_stdio{/*.in=*/{}, /*.out=*/wp});
wp.close();
bpv::process proc = l(ctx, pth, args, bpv::process_stdio{/*.in=*/{}, /*.out=*/rp});
std::string out;
bpv::error_code ec;
@@ -556,8 +572,8 @@ BOOST_AUTO_TEST_CASE(bind_launcher)
while (ec == asio::error::interrupted)
sz += asio::read(rp, asio::dynamic_buffer(out), ec);
BOOST_CHECK(sz != 0);
BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message());
BOOST_REQUIRE(sz != 0);
if (out.back() != '/' && target.string().back() == '/')
out += '/';