agent-enviroments/builder/libs/seastar/tests/unit/spawn_test.cc
2024-09-10 17:06:08 +03:00

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);
});
});
}