| #include <unistd.h> |
| |
| #include <memory> |
| #include <thread> |
| #include <chrono> |
| |
| #include "gtest/gtest.h" |
| |
| #include "aos/actions/actions.h" |
| #include "aos/actions/actions_generated.h" |
| #include "aos/actions/actor.h" |
| #include "aos/actions/test_action_generated.h" |
| #include "aos/events/simulated_event_loop.h" |
| #include "aos/testing/test_logging.h" |
| #include "aos/testing/test_shm.h" |
| |
| namespace aos { |
| namespace common { |
| namespace actions { |
| namespace testing { |
| |
| namespace chrono = ::std::chrono; |
| |
| class TestActorIndex |
| : public aos::common::actions::ActorBase<actions::TestActionGoal> { |
| public: |
| typedef TypedActionFactory<actions::TestActionGoal> Factory; |
| |
| explicit TestActorIndex(::aos::EventLoop *event_loop) |
| : aos::common::actions::ActorBase<actions::TestActionGoal>( |
| event_loop, "/test_action") {} |
| |
| static Factory MakeFactory(::aos::EventLoop *event_loop) { |
| return Factory(event_loop, "/test_action"); |
| } |
| |
| bool RunAction(const UInt *new_index) override { |
| VLOG(1) << "New index " << FlatbufferToJson(new_index); |
| index = new_index->val(); |
| return true; |
| } |
| |
| uint32_t index = 0; |
| }; |
| |
| class TestActorNOP |
| : public aos::common::actions::ActorBase<actions::TestActionGoal> { |
| public: |
| typedef TypedActionFactory<actions::TestActionGoal> Factory; |
| |
| explicit TestActorNOP(::aos::EventLoop *event_loop) |
| : actions::ActorBase<actions::TestActionGoal>( |
| event_loop, "/test_action") {} |
| |
| static Factory MakeFactory(::aos::EventLoop *event_loop) { |
| return Factory(event_loop, "/test_action"); |
| } |
| |
| bool RunAction(const UInt *) override { return true; } |
| }; |
| |
| class TestActorShouldCancel |
| : public aos::common::actions::ActorBase<actions::TestActionGoal> { |
| public: |
| typedef TypedActionFactory<actions::TestActionGoal> Factory; |
| |
| explicit TestActorShouldCancel(::aos::EventLoop *event_loop) |
| : aos::common::actions::ActorBase<actions::TestActionGoal>( |
| event_loop, "/test_action") {} |
| |
| static Factory MakeFactory(::aos::EventLoop *event_loop) { |
| return Factory(event_loop, "/test_action"); |
| } |
| |
| bool RunAction(const UInt *) override { |
| while (!ShouldCancel()) { |
| AOS_LOG(FATAL, "NOT CANCELED!!\n"); |
| } |
| return true; |
| } |
| }; |
| |
| class TestActor2Nop |
| : public aos::common::actions::ActorBase<actions::TestAction2Goal> { |
| public: |
| typedef TypedActionFactory<actions::TestAction2Goal> Factory; |
| |
| explicit TestActor2Nop(::aos::EventLoop *event_loop) |
| : actions::ActorBase<actions::TestAction2Goal>( |
| event_loop, "/test_action2") {} |
| |
| static Factory MakeFactory(::aos::EventLoop *event_loop) { |
| return Factory(event_loop, "/test_action2"); |
| } |
| |
| bool RunAction(const actions::MyParams *) { return true; } |
| }; |
| |
| class ActionTest : public ::testing::Test { |
| protected: |
| ActionTest() |
| : configuration_( |
| configuration::ReadConfig("aos/actions/action_test_config.json")), |
| event_loop_factory_(&configuration_.message()), |
| actor1_event_loop_(event_loop_factory_.MakeEventLoop("actor1")), |
| actor2_event_loop_(event_loop_factory_.MakeEventLoop("actor2")), |
| test_event_loop_(event_loop_factory_.MakeEventLoop("test")) { |
| ::aos::testing::EnableTestLogging(); |
| } |
| |
| void RunAt(monotonic_clock::time_point exec_time, std::function<void()> fn) { |
| TimerHandler *timer = test_event_loop_->AddTimer(fn); |
| test_event_loop_->OnRun([timer, exec_time]() { timer->Setup(exec_time); }); |
| } |
| |
| FlatbufferDetachedBuffer<Configuration> configuration_; |
| |
| // Bring up and down Core. |
| ::aos::SimulatedEventLoopFactory event_loop_factory_; |
| |
| ::std::unique_ptr<::aos::EventLoop> actor1_event_loop_; |
| ::std::unique_ptr<::aos::EventLoop> actor2_event_loop_; |
| ::std::unique_ptr<::aos::EventLoop> test_event_loop_; |
| }; |
| |
| // Tests that the the actions exist in a safe state at startup. |
| TEST_F(ActionTest, DoesNothing) { |
| ActionQueue action_queue; |
| // Tick an empty queue and make sure it was not running. |
| EXPECT_FALSE(action_queue.Running()); |
| action_queue.Tick(); |
| EXPECT_FALSE(action_queue.Running()); |
| } |
| |
| // Tests that starting with an old run message in the goal queue actually works. |
| // This used to result in the client hanging, waiting for a response to its |
| // cancel message. |
| TEST_F(ActionTest, StartWithOldGoal) { |
| ::std::unique_ptr<::aos::EventLoop> test2_event_loop = |
| event_loop_factory_.MakeEventLoop("test2"); |
| ::aos::Sender<TestActionGoal> goal_sender = |
| test2_event_loop->MakeSender<TestActionGoal>("/test_action"); |
| ::aos::Fetcher<Status> status_fetcher = |
| test2_event_loop->MakeFetcher<Status>("/test_action"); |
| |
| TestActorIndex::Factory nop_actor_factory = |
| TestActorNOP::MakeFactory(test_event_loop_.get()); |
| |
| ActionQueue action_queue; |
| |
| { |
| ::aos::Sender<TestActionGoal>::Builder builder = |
| goal_sender.MakeBuilder(); |
| |
| TestActionGoal::Builder goal_builder = |
| builder.MakeBuilder<TestActionGoal>(); |
| |
| goal_builder.add_run(971); |
| ASSERT_TRUE(builder.Send(goal_builder.Finish())); |
| } |
| |
| TestActorNOP nop_act(actor1_event_loop_.get()); |
| |
| ASSERT_FALSE(status_fetcher.Fetch()); |
| |
| RunAt(monotonic_clock::time_point(chrono::seconds(1)), [&]() { |
| ASSERT_TRUE(status_fetcher.Fetch()); |
| EXPECT_EQ(0u, status_fetcher->running()); |
| EXPECT_EQ(0u, status_fetcher->last_running()); |
| |
| { |
| UIntT uint; |
| uint.val = 0; |
| action_queue.EnqueueAction(nop_actor_factory.Make(uint)); |
| } |
| |
| // We started an action and it should be running. |
| EXPECT_TRUE(action_queue.Running()); |
| |
| action_queue.CancelAllActions(); |
| action_queue.Tick(); |
| |
| EXPECT_TRUE(action_queue.Running()); |
| }); |
| |
| // Run the action so it can signal completion. |
| RunAt(monotonic_clock::time_point(chrono::seconds(2)), [&]() { |
| action_queue.Tick(); |
| |
| // Make sure it stopped. |
| EXPECT_FALSE(action_queue.Running()); |
| }); |
| |
| event_loop_factory_.RunFor(chrono::seconds(3)); |
| } |
| |
| // Tests that an action starts and stops. |
| TEST_F(ActionTest, ActionQueueWasRunning) { |
| TestActorNOP nop_act(actor1_event_loop_.get()); |
| |
| TestActorIndex::Factory nop_actor_factory = |
| TestActorNOP::MakeFactory(test_event_loop_.get()); |
| |
| ActionQueue action_queue; |
| |
| // Tick an empty queue and make sure it was not running. |
| RunAt(monotonic_clock::time_point(chrono::seconds(1)), [&]() { |
| action_queue.Tick(); |
| EXPECT_FALSE(action_queue.Running()); |
| |
| { |
| UIntT uint; |
| uint.val = 0; |
| action_queue.EnqueueAction(nop_actor_factory.Make(uint)); |
| } |
| |
| // We started an action and it should be running. |
| EXPECT_TRUE(action_queue.Running()); |
| |
| // Tick it and make sure it is still running. |
| action_queue.Tick(); |
| EXPECT_TRUE(action_queue.Running()); |
| }); |
| |
| // Run the action so it can signal completion. |
| RunAt(monotonic_clock::time_point(chrono::seconds(2)), [&]() { |
| action_queue.Tick(); |
| |
| // Make sure it stopped. |
| EXPECT_FALSE(action_queue.Running()); |
| }); |
| |
| event_loop_factory_.RunFor(chrono::seconds(3)); |
| } |
| |
| // Tests that we can cancel two actions and have them both stop. |
| TEST_F(ActionTest, ActionQueueCancelAll) { |
| TestActorNOP nop_act(actor1_event_loop_.get()); |
| |
| TestActorIndex::Factory nop_actor_factory = |
| TestActorNOP::MakeFactory(test_event_loop_.get()); |
| |
| ActionQueue action_queue; |
| |
| // Let the actor and action queue start up and confirm nothing is running. |
| RunAt(monotonic_clock::time_point(chrono::seconds(1)), [&]() { |
| action_queue.Tick(); |
| |
| EXPECT_FALSE(action_queue.Running()); |
| |
| // Enqueue two actions to test both cancel. We can have an action and a next |
| // action so we want to test that. |
| { |
| UIntT uint; |
| uint.val = 0; |
| action_queue.EnqueueAction(nop_actor_factory.Make(uint)); |
| action_queue.EnqueueAction(nop_actor_factory.Make(uint)); |
| } |
| |
| action_queue.Tick(); |
| |
| // Check that current and next exist. |
| EXPECT_TRUE(action_queue.GetCurrentActionState(nullptr, nullptr, nullptr, |
| nullptr, nullptr, nullptr)); |
| EXPECT_TRUE(action_queue.GetNextActionState(nullptr, nullptr, nullptr, |
| nullptr, nullptr, nullptr)); |
| |
| action_queue.CancelAllActions(); |
| action_queue.Tick(); |
| |
| // It should still be running as the actor could not have signaled. |
| EXPECT_TRUE(action_queue.Running()); |
| |
| bool sent_started, sent_cancel, interrupted; |
| EXPECT_TRUE(action_queue.GetCurrentActionState( |
| nullptr, &sent_started, &sent_cancel, &interrupted, nullptr, nullptr)); |
| EXPECT_TRUE(sent_started); |
| EXPECT_TRUE(sent_cancel); |
| EXPECT_FALSE(interrupted); |
| |
| EXPECT_FALSE(action_queue.GetNextActionState(nullptr, nullptr, nullptr, |
| nullptr, nullptr, nullptr)); |
| }); |
| |
| // Run the action so it can signal completion. |
| RunAt(monotonic_clock::time_point(chrono::seconds(2)), [&]() { |
| action_queue.Tick(); |
| |
| // Make sure it stopped. |
| EXPECT_FALSE(action_queue.Running()); |
| EXPECT_EQ(1, nop_act.running_count()); |
| }); |
| |
| event_loop_factory_.RunFor(chrono::seconds(3)); |
| } |
| |
| // Tests that an action that would block forever stops when canceled. |
| TEST_F(ActionTest, ActionQueueCancelOne) { |
| TestActorShouldCancel cancel_act(actor1_event_loop_.get()); |
| |
| TestActorShouldCancel::Factory cancel_action_factory = |
| TestActorShouldCancel::MakeFactory(test_event_loop_.get()); |
| |
| ActionQueue action_queue; |
| |
| // Let the actor and action queue start up. |
| RunAt(monotonic_clock::time_point(chrono::seconds(1)), [&]() { |
| action_queue.Tick(); |
| |
| // Enqueue blocking action. |
| { |
| UIntT uint; |
| uint.val = 0; |
| action_queue.EnqueueAction(cancel_action_factory.Make(uint)); |
| } |
| |
| action_queue.Tick(); |
| EXPECT_TRUE(action_queue.Running()); |
| |
| // Tell action to cancel. |
| action_queue.CancelCurrentAction(); |
| action_queue.Tick(); |
| }); |
| |
| // This will block forever on failure. |
| // TODO(ben): prolly a bad way to fail |
| RunAt(monotonic_clock::time_point(chrono::seconds(2)), [&]() { |
| action_queue.Tick(); |
| |
| // It should still be running as the actor could not have signalled. |
| EXPECT_FALSE(action_queue.Running()); |
| }); |
| |
| event_loop_factory_.RunFor(chrono::seconds(3)); |
| } |
| |
| // Tests that 2 actions in a row causes the second one to cancel the first one. |
| TEST_F(ActionTest, ActionQueueTwoActions) { |
| TestActorNOP nop_actor(actor1_event_loop_.get()); |
| |
| TestActorIndex::Factory nop_actor_factory = |
| TestActorNOP::MakeFactory(test_event_loop_.get()); |
| |
| ActionQueue action_queue; |
| |
| // id for the first time run. |
| uint32_t nop_actor_id = 0; |
| // Check the internal state and write down id for later use. |
| bool sent_started, sent_cancel, interrupted; |
| // id for the second run. |
| uint32_t nop_actor2_id = 0; |
| |
| // Tick an empty queue and make sure it was not running. |
| RunAt(monotonic_clock::time_point(chrono::seconds(1)), [&]() { |
| action_queue.Tick(); |
| EXPECT_FALSE(action_queue.Running()); |
| |
| // Enqueue action to be canceled. |
| { |
| UIntT uint; |
| uint.val = 0; |
| action_queue.EnqueueAction(nop_actor_factory.Make(uint)); |
| } |
| action_queue.Tick(); |
| |
| // Should still be running as the actor could not have signalled. |
| EXPECT_TRUE(action_queue.Running()); |
| |
| EXPECT_TRUE(action_queue.GetCurrentActionState(nullptr, &sent_started, |
| &sent_cancel, &interrupted, |
| &nop_actor_id, nullptr)); |
| EXPECT_TRUE(sent_started); |
| EXPECT_FALSE(sent_cancel); |
| EXPECT_FALSE(interrupted); |
| ASSERT_NE(0u, nop_actor_id); |
| |
| // Add the next action which should ensure the first stopped. |
| { |
| UIntT uint; |
| uint.val = 0; |
| action_queue.EnqueueAction(nop_actor_factory.Make(uint)); |
| } |
| |
| // Check the internal state and write down id for later use. |
| EXPECT_TRUE(action_queue.GetNextActionState(nullptr, &sent_started, |
| &sent_cancel, &interrupted, |
| &nop_actor2_id, nullptr)); |
| EXPECT_NE(nop_actor_id, nop_actor2_id); |
| EXPECT_FALSE(sent_started); |
| EXPECT_FALSE(sent_cancel); |
| EXPECT_FALSE(interrupted); |
| ASSERT_NE(0u, nop_actor2_id); |
| |
| action_queue.Tick(); |
| }); |
| |
| // Run the action so it can signal completion. |
| RunAt(monotonic_clock::time_point(chrono::seconds(2)), [&]() { |
| action_queue.Tick(); |
| |
| // Check the new action is the right one. |
| uint32_t test_id = 0; |
| EXPECT_TRUE(action_queue.GetCurrentActionState( |
| nullptr, &sent_started, &sent_cancel, &interrupted, &test_id, nullptr)); |
| EXPECT_TRUE(sent_started); |
| EXPECT_FALSE(sent_cancel); |
| EXPECT_FALSE(interrupted); |
| EXPECT_EQ(nop_actor2_id, test_id); |
| |
| // Make sure it is still going. |
| EXPECT_TRUE(action_queue.Running()); |
| }); |
| |
| // Now let everything finish. |
| RunAt(monotonic_clock::time_point(chrono::seconds(3)), [&]() { |
| action_queue.Tick(); |
| |
| // Make sure it stopped. |
| EXPECT_FALSE(action_queue.Running()); |
| }); |
| |
| event_loop_factory_.RunFor(chrono::seconds(4)); |
| } |
| |
| // Tests that we do get an index with our goal |
| TEST_F(ActionTest, ActionIndex) { |
| TestActorIndex idx_actor(actor1_event_loop_.get()); |
| |
| TestActorIndex::Factory test_actor_index_factory = |
| TestActorIndex::MakeFactory(test_event_loop_.get()); |
| ::aos::Fetcher<actions::TestActionGoal> goal_fetcher_ = |
| test_event_loop_->MakeFetcher<actions::TestActionGoal>("/test_action"); |
| |
| ActionQueue action_queue; |
| // Tick an empty queue and make sure it was not running. Also tick the |
| // factory to allow it to send out the initial cancel message. |
| RunAt(monotonic_clock::time_point(chrono::seconds(1)), [&]() { |
| action_queue.Tick(); |
| |
| EXPECT_FALSE(action_queue.Running()); |
| |
| // Enqueue action to post index. |
| { |
| UIntT uint; |
| uint.val = 5; |
| action_queue.EnqueueAction(test_actor_index_factory.Make(uint)); |
| } |
| |
| ASSERT_TRUE(goal_fetcher_.Fetch()); |
| EXPECT_EQ(5u, goal_fetcher_->params()->val()); |
| EXPECT_EQ(0u, idx_actor.index); |
| |
| action_queue.Tick(); |
| }); |
| |
| // Run the next action so it can accomplish signal completion. |
| RunAt(monotonic_clock::time_point(chrono::seconds(2)), [&]() { |
| action_queue.Tick(); |
| EXPECT_EQ(5u, idx_actor.index); |
| |
| // Enqueue action to post index. |
| { |
| UIntT uint; |
| uint.val = 3; |
| action_queue.EnqueueAction(test_actor_index_factory.Make(uint)); |
| } |
| ASSERT_TRUE(goal_fetcher_.Fetch()); |
| EXPECT_EQ(3u, goal_fetcher_->params()->val()); |
| }); |
| |
| // Run the next action so it can accomplish signal completion. |
| RunAt(monotonic_clock::time_point(chrono::seconds(3)), [&]() { |
| action_queue.Tick(); |
| EXPECT_EQ(3u, idx_actor.index); |
| }); |
| |
| event_loop_factory_.RunFor(chrono::seconds(3)); |
| } |
| |
| // Tests that an action with a structure params works. |
| TEST_F(ActionTest, StructParamType) { |
| TestActor2Nop nop_actor(actor2_event_loop_.get()); |
| |
| TestActor2Nop::Factory test_action_2_nop_factory = |
| TestActor2Nop::MakeFactory(test_event_loop_.get()); |
| |
| ActionQueue action_queue; |
| |
| RunAt(monotonic_clock::time_point(chrono::seconds(0)), [&]() { |
| // Tick an empty queue and make sure it was not running. |
| action_queue.Tick(); |
| EXPECT_FALSE(action_queue.Running()); |
| |
| actions::MyParamsT p; |
| p.param1 = 5.0; |
| p.param2 = 7; |
| |
| action_queue.EnqueueAction(test_action_2_nop_factory.Make(p)); |
| |
| // We started an action and it should be running. |
| EXPECT_TRUE(action_queue.Running()); |
| |
| // Tick it and make sure it is still running. |
| action_queue.Tick(); |
| EXPECT_TRUE(action_queue.Running()); |
| }); |
| |
| // Run the action so it can signal completion. |
| // The actor takes no time, but running for a second is the best way to get it |
| // to go. |
| RunAt(monotonic_clock::time_point(chrono::seconds(1)), [&]() { |
| action_queue.Tick(); |
| |
| // Make sure it stopped. |
| EXPECT_FALSE(action_queue.Running()); |
| }); |
| |
| event_loop_factory_.RunFor(chrono::seconds(2)); |
| } |
| |
| } // namespace testing |
| } // namespace actions |
| } // namespace common |
| } // namespace aos |