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