blob: 87cb5446eb7b101fcd9b52e1a1cb6921626b7ce1 [file] [log] [blame]
#include <csignal>
#include <future>
#include <thread>
#include "aos/events/ping_generated.h"
#include "aos/events/pong_generated.h"
#include "aos/network/team_number.h"
#include "aos/testing/path.h"
#include "aos/testing/tmpdir.h"
#include "aos/util/file.h"
#include "gtest/gtest.h"
#include "starter_rpc_lib.h"
#include "starterd_lib.h"
using aos::testing::ArtifactPath;
namespace aos {
namespace starter {
class StarterdTest : public ::testing::Test {
public:
StarterdTest() : shm_dir_(aos::testing::TestTmpDir() + "/aos") {
FLAGS_shm_base = shm_dir_;
// Nuke the shm dir:
aos::util::UnlinkRecursive(shm_dir_);
}
protected:
void SetupStarterCleanup(aos::starter::Starter *starter) {
starter->event_loop()
->AddTimer([this, starter]() {
if (test_done_) {
starter->Cleanup();
}
})
->Setup(starter->event_loop()->monotonic_now(),
std::chrono::seconds(1));
}
gflags::FlagSaver flag_saver_;
std::string shm_dir_;
// Used to track when the test completes so that we can clean up the starter
// in its thread.
std::atomic<bool> test_done_{false};
};
struct TestParams {
std::string config;
std::string hostname;
};
class StarterdConfigParamTest
: public StarterdTest,
public ::testing::WithParamInterface<TestParams> {};
TEST_P(StarterdConfigParamTest, MultiNodeStartStopTest) {
gflags::FlagSaver flag_saver;
FLAGS_override_hostname = GetParam().hostname;
const std::string config_file = ArtifactPath(GetParam().config);
aos::FlatbufferDetachedBuffer<aos::Configuration> config =
aos::configuration::ReadConfig(config_file);
auto new_config = aos::configuration::MergeWithConfig(
&config.message(),
absl::StrFormat(
R"({"applications": [
{
"name": "ping",
"executable_name": "%s",
"nodes": ["pi1"],
"args": ["--shm_base", "%s", "--config", "%s", "--override_hostname", "%s"]
},
{
"name": "pong",
"executable_name": "%s",
"nodes": ["pi1"],
"args": ["--shm_base", "%s", "--config", "%s", "--override_hostname", "%s"]
}
]})",
ArtifactPath("aos/events/ping"), shm_dir_, config_file,
GetParam().hostname, ArtifactPath("aos/events/pong"), shm_dir_,
config_file, GetParam().hostname));
const aos::Configuration *config_msg = &new_config.message();
// Set up starter with config file
aos::starter::Starter starter(config_msg);
// Create an event loop to watch for ping messages, verifying it actually
// started.
aos::ShmEventLoop watcher_loop(config_msg);
watcher_loop.SkipAosLog();
aos::ShmEventLoop client_loop(config_msg);
client_loop.SkipAosLog();
StarterClient client(&client_loop);
client.SetTimeoutHandler(
[]() { FAIL() << ": Command should not have timed out."; });
bool success = false;
client.SetSuccessHandler([&success, &client_loop]() {
client_loop.Exit();
success = true;
});
watcher_loop
.AddTimer([&watcher_loop] {
watcher_loop.Exit();
FAIL();
})
->Setup(watcher_loop.monotonic_now() + std::chrono::seconds(7));
std::atomic<int> test_stage = 0;
// Watch on the client loop since we need to interact with the StarterClient.
client_loop.MakeWatcher("/test", [&test_stage, &client,
&client_loop](const aos::examples::Ping &) {
switch (test_stage) {
case 1: {
test_stage = 2;
break;
}
case 2: {
{
client.SendCommands({{Command::STOP, "ping", {client_loop.node()}}},
std::chrono::seconds(3));
}
test_stage = 3;
break;
}
}
});
watcher_loop.MakeWatcher(
"/aos", [&test_stage, &watcher_loop](const aos::starter::Status &status) {
const aos::starter::ApplicationStatus *app_status =
FindApplicationStatus(status, "ping");
if (app_status == nullptr) {
return;
}
switch (test_stage) {
case 0: {
if (app_status->has_state() &&
app_status->state() == aos::starter::State::RUNNING) {
test_stage = 1;
}
break;
}
case 3: {
if (app_status->has_state() &&
app_status->state() == aos::starter::State::STOPPED) {
watcher_loop.Exit();
SUCCEED();
}
break;
}
}
});
SetupStarterCleanup(&starter);
std::thread starterd_thread([&starter] { starter.Run(); });
std::thread client_thread([&client_loop] { client_loop.Run(); });
watcher_loop.Run();
test_done_ = true;
client_thread.join();
starterd_thread.join();
}
INSTANTIATE_TEST_SUITE_P(
StarterdConfigParamTest, StarterdConfigParamTest,
::testing::Values(TestParams{"aos/events/pingpong_config.json", ""},
TestParams{"aos/starter/multinode_pingpong_config.json",
"pi1"}));
TEST_F(StarterdTest, DeathTest) {
const std::string config_file =
ArtifactPath("aos/events/pingpong_config.json");
aos::FlatbufferDetachedBuffer<aos::Configuration> config =
aos::configuration::ReadConfig(config_file);
auto new_config = aos::configuration::MergeWithConfig(
&config.message(), absl::StrFormat(
R"({"applications": [
{
"name": "ping",
"executable_name": "%s",
"args": ["--shm_base", "%s"]
},
{
"name": "pong",
"executable_name": "%s",
"args": ["--shm_base", "%s"]
}
]})",
ArtifactPath("aos/events/ping"), shm_dir_,
ArtifactPath("aos/events/pong"), shm_dir_));
const aos::Configuration *config_msg = &new_config.message();
// Set up starter with config file
aos::starter::Starter starter(config_msg);
// Create an event loop to watch for ping messages, verifying it actually
// started.
aos::ShmEventLoop watcher_loop(config_msg);
watcher_loop.SkipAosLog();
watcher_loop
.AddTimer([&watcher_loop] {
watcher_loop.Exit();
FAIL();
})
->Setup(watcher_loop.monotonic_now() + std::chrono::seconds(11));
int test_stage = 0;
uint64_t id;
watcher_loop.MakeWatcher("/aos", [&test_stage, &watcher_loop,
&id](const aos::starter::Status &status) {
const aos::starter::ApplicationStatus *app_status =
FindApplicationStatus(status, "ping");
if (app_status == nullptr) {
return;
}
switch (test_stage) {
case 0: {
if (app_status->has_state() &&
app_status->state() == aos::starter::State::RUNNING) {
LOG(INFO) << "Ping is running";
test_stage = 1;
ASSERT_TRUE(app_status->has_pid());
ASSERT_TRUE(kill(app_status->pid(), SIGINT) != -1);
ASSERT_TRUE(app_status->has_id());
id = app_status->id();
}
break;
}
case 1: {
if (app_status->has_state() &&
app_status->state() == aos::starter::State::RUNNING &&
app_status->has_id() && app_status->id() != id) {
LOG(INFO) << "Ping restarted";
watcher_loop.Exit();
SUCCEED();
}
break;
}
}
});
SetupStarterCleanup(&starter);
std::thread starterd_thread([&starter] { starter.Run(); });
watcher_loop.Run();
test_done_ = true;
starterd_thread.join();
}
TEST_F(StarterdTest, Autostart) {
const std::string config_file =
ArtifactPath("aos/events/pingpong_config.json");
aos::FlatbufferDetachedBuffer<aos::Configuration> config =
aos::configuration::ReadConfig(config_file);
auto new_config = aos::configuration::MergeWithConfig(
&config.message(), absl::StrFormat(
R"({"applications": [
{
"name": "ping",
"executable_name": "%s",
"args": ["--shm_base", "%s"],
"autostart": false
},
{
"name": "pong",
"executable_name": "%s",
"args": ["--shm_base", "%s"]
}
]})",
ArtifactPath("aos/events/ping"), shm_dir_,
ArtifactPath("aos/events/pong"), shm_dir_));
const aos::Configuration *config_msg = &new_config.message();
// Set up starter with config file
aos::starter::Starter starter(config_msg);
// Create an event loop to watch for the application starting up.
aos::ShmEventLoop watcher_loop(config_msg);
watcher_loop.SkipAosLog();
watcher_loop
.AddTimer([&watcher_loop] {
watcher_loop.Exit();
FAIL();
})
->Setup(watcher_loop.monotonic_now() + std::chrono::seconds(7));
int pong_running_count = 0;
watcher_loop.MakeWatcher("/aos", [&watcher_loop, &pong_running_count](
const aos::starter::Status &status) {
const aos::starter::ApplicationStatus *ping_app_status =
FindApplicationStatus(status, "ping");
const aos::starter::ApplicationStatus *pong_app_status =
FindApplicationStatus(status, "pong");
if (ping_app_status == nullptr || pong_app_status == nullptr) {
return;
}
if (ping_app_status->has_state() &&
ping_app_status->state() != aos::starter::State::STOPPED) {
watcher_loop.Exit();
FAIL();
}
if (pong_app_status->has_state() &&
pong_app_status->state() == aos::starter::State::RUNNING) {
++pong_running_count;
// Sometimes if the timing for everything is *just* off, then the
// process_info will say that the process name is "starter_test" because
// it grabbed the name after the fork() but before the execvp(). To
// protect against that, wait an extra cycle. If things aren't fixed by
// the second cycle, then that is a problem.
if (pong_running_count < 2) {
return;
}
ASSERT_TRUE(pong_app_status->has_process_info());
ASSERT_EQ("pong", pong_app_status->process_info()->name()->string_view())
<< aos::FlatbufferToJson(&status);
ASSERT_EQ(pong_app_status->pid(), pong_app_status->process_info()->pid());
ASSERT_TRUE(pong_app_status->process_info()->has_cpu_usage());
ASSERT_LE(0.0, pong_app_status->process_info()->cpu_usage());
watcher_loop.Exit();
SUCCEED();
}
});
SetupStarterCleanup(&starter);
std::thread starterd_thread([&starter] { starter.Run(); });
watcher_loop.Run();
test_done_ = true;
starterd_thread.join();
}
// Tests that starterd respects autorestart.
TEST_F(StarterdTest, DeathNoRestartTest) {
const std::string config_file =
ArtifactPath("aos/events/pingpong_config.json");
aos::FlatbufferDetachedBuffer<aos::Configuration> config =
aos::configuration::ReadConfig(config_file);
const std::string test_dir = aos::testing::TestTmpDir();
auto new_config = aos::configuration::MergeWithConfig(
&config.message(), absl::StrFormat(
R"({"applications": [
{
"name": "ping",
"executable_name": "%s",
"args": ["--shm_base", "%s/aos"],
"autorestart": false
},
{
"name": "pong",
"executable_name": "%s",
"args": ["--shm_base", "%s/aos"]
}
]})",
ArtifactPath("aos/events/ping"), test_dir,
ArtifactPath("aos/events/pong"), test_dir));
const aos::Configuration *config_msg = &new_config.message();
// Set up starter with config file
aos::starter::Starter starter(config_msg);
// Create an event loop to watch for the Status message to watch the state
// transitions.
aos::ShmEventLoop watcher_loop(config_msg);
watcher_loop.SkipAosLog();
watcher_loop
.AddTimer([&watcher_loop] {
watcher_loop.Exit();
SUCCEED();
})
->Setup(watcher_loop.monotonic_now() + std::chrono::seconds(11));
int test_stage = 0;
uint64_t id;
watcher_loop.MakeWatcher("/aos", [&test_stage, &watcher_loop,
&id](const aos::starter::Status &status) {
const aos::starter::ApplicationStatus *app_status =
FindApplicationStatus(status, "ping");
if (app_status == nullptr) {
return;
}
switch (test_stage) {
case 0: {
if (app_status->has_state() &&
app_status->state() == aos::starter::State::RUNNING) {
LOG(INFO) << "Ping is running";
test_stage = 1;
ASSERT_TRUE(app_status->has_pid());
ASSERT_TRUE(kill(app_status->pid(), SIGINT) != -1);
ASSERT_TRUE(app_status->has_id());
id = app_status->id();
}
break;
}
case 1: {
if (app_status->has_state() &&
app_status->state() == aos::starter::State::RUNNING &&
app_status->has_id() && app_status->id() != id) {
LOG(INFO) << "Ping restarted, it shouldn't...";
watcher_loop.Exit();
FAIL();
}
break;
}
}
});
SetupStarterCleanup(&starter);
std::thread starterd_thread([&starter] { starter.Run(); });
watcher_loop.Run();
test_done_ = true;
starterd_thread.join();
}
} // namespace starter
} // namespace aos