// Copyright 2022 The Centipede Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://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. #include "./centipede/command.h" #include #include // NOLINT(for WTERMSIG) #include #include // NOLINT #include #include #include #include "gtest/gtest.h" #include "absl/log/log.h" #include "absl/strings/substitute.h" #include "absl/time/time.h" #include "./centipede/stop.h" #include "./centipede/util.h" #include "./common/test_util.h" namespace fuzztest::internal { namespace { TEST(CommandTest, ToString) { EXPECT_EQ(Command{"x"}.ToString(), "env \\\nx"); { Command::Options cmd_options; cmd_options.args = {"arg1", "arg2"}; EXPECT_EQ((Command{"path", std::move(cmd_options)}.ToString()), "env \\\npath \\\narg1 \\\narg2"); } { Command::Options cmd_options; cmd_options.env_add = {"K1=V1", "K2=V2"}; cmd_options.env_remove = {"K3"}; EXPECT_EQ((Command{"x", std::move(cmd_options)}.ToString()), "env \\\n-u K3 \\\nK1=V1 \\\nK2=V2 \\\nx"); } { Command::Options cmd_options; cmd_options.stdout_file = "out"; EXPECT_EQ((Command{"x", std::move(cmd_options)}.ToString()), "env \\\nx \\\n> out"); } { Command::Options cmd_options; cmd_options.stderr_file = "err"; EXPECT_EQ((Command{"x", std::move(cmd_options)}.ToString()), "env \\\nx \\\n2> err"); } { Command::Options cmd_options; cmd_options.stdout_file = "out"; cmd_options.stderr_file = "err"; EXPECT_EQ((Command{"x", std::move(cmd_options)}.ToString()), "env \\\nx \\\n> out \\\n2> err"); } { Command::Options cmd_options; cmd_options.stdout_file = "out"; cmd_options.stderr_file = "out"; EXPECT_EQ((Command{"x", std::move(cmd_options)}.ToString()), "env \\\nx \\\n> out \\\n2>&1"); } } TEST(CommandTest, Execute) { // Check for default exit code. Command echo{"echo"}; EXPECT_EQ(echo.Execute(), 0); EXPECT_FALSE(ShouldStop()); // Check for exit code 7. Command exit7{"bash -c 'exit 7'"}; EXPECT_EQ(exit7.Execute(), 7); EXPECT_FALSE(ShouldStop()); } TEST(CommandDeathTest, Execute) { GTEST_FLAG_SET(death_test_style, "threadsafe"); // Test for interrupt handling. const auto self_sigint_lambda = []() { Command self_sigint{"bash -c 'kill -SIGINT $$'"}; self_sigint.Execute(); if (ShouldStop()) { LOG(INFO) << "Early stop requested"; exit(ExitCode()); } }; EXPECT_DEATH(self_sigint_lambda(), "Early stop requested"); } TEST(CommandTest, InputFileWildCard) { Command::Options cmd_options; cmd_options.timeout = absl::Seconds(2); cmd_options.temp_file_path = "TEMP_FILE"; Command cmd{"foo bar @@ baz", std::move(cmd_options)}; EXPECT_EQ(cmd.ToString(), "env \\\nfoo bar TEMP_FILE baz"); } TEST(CommandTest, ForkServer) { const std::string test_tmpdir = GetTestTempDir(test_info_->name()); const std::string helper = GetDataDependencyFilepath("centipede/command_test_helper"); // TODO(ussuri): Dedupe these testcases. { const std::string input = "success"; const std::string log = std::filesystem::path{test_tmpdir} / input; Command::Options cmd_options; cmd_options.args = {input}; cmd_options.stdout_file = log; cmd_options.stderr_file = log; Command cmd{helper, std::move(cmd_options)}; EXPECT_TRUE(cmd.StartForkServer(test_tmpdir, "ForkServer")); EXPECT_EQ(cmd.Execute(), EXIT_SUCCESS); std::string log_contents; ReadFromLocalFile(log, log_contents); EXPECT_EQ(log_contents, absl::Substitute("Got input: $0", input)); } { const std::string input = "fail"; const std::string log = std::filesystem::path{test_tmpdir} / input; Command::Options cmd_options; cmd_options.args = {input}; cmd_options.stdout_file = log; cmd_options.stderr_file = log; Command cmd{helper, std::move(cmd_options)}; EXPECT_TRUE(cmd.StartForkServer(test_tmpdir, "ForkServer")); EXPECT_EQ(cmd.Execute(), EXIT_FAILURE); std::string log_contents; ReadFromLocalFile(log, log_contents); EXPECT_EQ(log_contents, absl::Substitute("Got input: $0", input)); } { const std::string input = "ret42"; const std::string log = std::filesystem::path{test_tmpdir} / input; Command::Options cmd_options; cmd_options.args = {input}; cmd_options.stdout_file = log; cmd_options.stderr_file = log; Command cmd{helper, std::move(cmd_options)}; EXPECT_TRUE(cmd.StartForkServer(test_tmpdir, "ForkServer")); EXPECT_EQ(cmd.Execute(), 42); std::string log_contents; ReadFromLocalFile(log, log_contents); EXPECT_EQ(log_contents, absl::Substitute("Got input: $0", input)); } { const std::string input = "abort"; const std::string log = std::filesystem::path{test_tmpdir} / input; Command::Options cmd_options; cmd_options.args = {input}; cmd_options.stdout_file = log; cmd_options.stderr_file = log; Command cmd{helper, std::move(cmd_options)}; EXPECT_TRUE(cmd.StartForkServer(test_tmpdir, "ForkServer")); // WTERMSIG() needs an lvalue on some platforms. const int ret = cmd.Execute(); EXPECT_EQ(WTERMSIG(ret), SIGABRT); std::string log_contents; ReadFromLocalFile(log, log_contents); EXPECT_EQ(log_contents, absl::Substitute("Got input: $0", input)); } { const std::string input = "hang"; const std::string log = std::filesystem::path{test_tmpdir} / input; Command::Options cmd_options; cmd_options.args = {input}; cmd_options.stdout_file = log; cmd_options.stderr_file = log; cmd_options.timeout = absl::Seconds(2); Command cmd{helper, std::move(cmd_options)}; ASSERT_TRUE(cmd.StartForkServer(test_tmpdir, "ForkServer")); EXPECT_EQ(cmd.Execute(), EXIT_FAILURE); std::string log_contents; ReadFromLocalFile(log, log_contents); EXPECT_EQ(log_contents, absl::Substitute("Got input: $0", input)); } // TODO(kcc): [impl] test what happens if the child is interrupted. } } // namespace } // namespace fuzztest::internal