149 lines
6.5 KiB
C++
149 lines
6.5 KiB
C++
/*
|
|
* This file is open source software, licensed to you under the terms
|
|
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
|
* distributed with this work for additional information regarding copyright
|
|
* ownership. You may not use this file except in compliance with the License.
|
|
*
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2022 Kefu Chai ( tchaikov@gmail.com )
|
|
*/
|
|
#include <seastar/core/seastar.hh>
|
|
#include <seastar/testing/test_case.hh>
|
|
#include <seastar/util/log.hh>
|
|
#include <seastar/util/process.hh>
|
|
|
|
using namespace seastar;
|
|
using namespace seastar::experimental;
|
|
|
|
static seastar::logger testlog("testlog");
|
|
|
|
SEASTAR_TEST_CASE(test_spawn_success) {
|
|
return spawn_process("/bin/true").then([] (auto process) {
|
|
return process.wait();
|
|
}).then([] (auto wstatus) {
|
|
auto* exit_status = std::get_if<process::wait_exited>(&wstatus);
|
|
BOOST_REQUIRE(exit_status != nullptr);
|
|
BOOST_CHECK_EQUAL(exit_status->exit_code, EXIT_SUCCESS);
|
|
});
|
|
}
|
|
|
|
SEASTAR_TEST_CASE(test_spawn_failure) {
|
|
return spawn_process("/bin/false").then([] (auto process) {
|
|
return process.wait();
|
|
}).then([] (auto wstatus) {
|
|
auto* exit_status = std::get_if<process::wait_exited>(&wstatus);
|
|
BOOST_REQUIRE(exit_status != nullptr);
|
|
BOOST_CHECK_EQUAL(exit_status->exit_code, EXIT_FAILURE);
|
|
});
|
|
}
|
|
|
|
SEASTAR_TEST_CASE(test_spawn_program_does_not_exist) {
|
|
return spawn_process("non/existent/path").then_wrapped([] (future<process> fut) {
|
|
BOOST_REQUIRE(fut.failed());
|
|
BOOST_CHECK_EXCEPTION(std::rethrow_exception(fut.get_exception()),
|
|
std::system_error,
|
|
[](const auto& e) {
|
|
return e.code().value() == ENOENT;
|
|
});
|
|
});
|
|
}
|
|
|
|
SEASTAR_TEST_CASE(test_spawn_echo) {
|
|
const char* echo_cmd = "/bin/echo";
|
|
return spawn_process(echo_cmd, {.argv = {echo_cmd, "-n", "hello", "world"}}).then([] (auto process) {
|
|
auto cout = process.cout();
|
|
return do_with(std::move(process), std::move(cout), bool(false), [](auto& p, auto& cout, auto& matched) {
|
|
using consumption_result_type = typename input_stream<char>::consumption_result_type;
|
|
using stop_consuming_type = typename consumption_result_type::stop_consuming_type;
|
|
using tmp_buf = stop_consuming_type::tmp_buf;
|
|
struct consumer {
|
|
consumer(std::string_view expected, bool& matched)
|
|
: _expected(expected), _matched(matched) {}
|
|
future<consumption_result_type> operator()(tmp_buf buf) {
|
|
if (!std::equal(buf.begin(), buf.end(), _expected.begin())) {
|
|
_matched = false;
|
|
return make_ready_future<consumption_result_type>(stop_consuming_type({}));
|
|
}
|
|
_expected.remove_prefix(buf.size());
|
|
if (_expected.empty()) {
|
|
_matched = true;
|
|
return make_ready_future<consumption_result_type>(stop_consuming_type({}));
|
|
}
|
|
return make_ready_future<consumption_result_type>(continue_consuming{});
|
|
}
|
|
std::string_view _expected;
|
|
bool& _matched;
|
|
};
|
|
return cout.consume(consumer("hello world", matched)).then([&matched] {
|
|
BOOST_CHECK(matched);
|
|
}).finally([&p] {
|
|
return p.wait().discard_result();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
SEASTAR_TEST_CASE(test_spawn_input) {
|
|
static const sstring text = "hello world\n";
|
|
return spawn_process("/bin/cat").then([] (auto process) {
|
|
auto cin = process.cin();
|
|
auto cout = process.cout();
|
|
return do_with(std::move(process), std::move(cin), std::move(cout), [](auto& p, auto& cin, auto& cout) {
|
|
return cin.write(text).then([&cin] {
|
|
return cin.close();
|
|
}).handle_exception_type([] (std::system_error& e) {
|
|
BOOST_TEST_ERROR(fmt::format("failed to write to stdin: {}", e));
|
|
}).then([&cout] {
|
|
return cout.read_exactly(text.size());
|
|
}).handle_exception_type([] (std::system_error& e) {
|
|
BOOST_TEST_ERROR(fmt::format("failed to read from cout: {}", e));
|
|
return make_ready_future<temporary_buffer<char>>();
|
|
}).then([] (temporary_buffer<char> echo) {
|
|
BOOST_CHECK_EQUAL(sstring(echo.get(), echo.size()), text);
|
|
}).finally([&p] {
|
|
return p.wait().then([](process::wait_status wstatus) {
|
|
auto* exit_status = std::get_if<process::wait_exited>(&wstatus);
|
|
BOOST_REQUIRE(exit_status != nullptr);
|
|
BOOST_CHECK_EQUAL(exit_status->exit_code, EXIT_SUCCESS);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
SEASTAR_TEST_CASE(test_spawn_kill) {
|
|
const char* sleep_cmd = "/bin/sleep";
|
|
// sleep for 10s, but terminate it right away.
|
|
return spawn_process(sleep_cmd, {.argv = {sleep_cmd, "10"}}).then([] (auto process) {
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
return do_with(std::move(process), [](auto& p) {
|
|
p.terminate();
|
|
return p.wait();
|
|
}).then([start](experimental::process::wait_status wait_status) {
|
|
auto* wait_signaled = std::get_if<experimental::process::wait_signaled>(&wait_status);
|
|
BOOST_REQUIRE(wait_signaled != nullptr);
|
|
BOOST_CHECK_EQUAL(wait_signaled->terminating_signal, SIGTERM);
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
|
// sleep should be terminated in 10ms.
|
|
// pidfd_open(2) may fail and thus p.wait() falls back to
|
|
// waitpid(2) with backoff (at least 20ms).
|
|
// the minimal backoff is added to 10ms, so the test can pass on
|
|
// older kernels as well.
|
|
BOOST_CHECK_LE(ms, 10 + 20);
|
|
});
|
|
});
|
|
}
|