| // Copyright 2024 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "heartd/daemon/heartbeat_tracker.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/functional/callback_forward.h" |
| #include <base/notreached.h> |
| #include <base/test/task_environment.h> |
| #include <base/test/test_future.h> |
| #include <base/time/time.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <mojo/public/cpp/bindings/remote.h> |
| |
| #include "heartd/mojom/heartd.mojom.h" |
| |
| namespace heartd { |
| |
| namespace { |
| |
| namespace mojom = ::ash::heartd::mojom; |
| |
| class HeartbeatTrackerTest : public testing::Test { |
| public: |
| HeartbeatTrackerTest() { |
| heartbeat_tracker_ = std::make_unique<HeartbeatTracker>( |
| mojom::ServiceName::kKiosk, pacemaker_.BindNewPipeAndPassReceiver()); |
| } |
| ~HeartbeatTrackerTest() override = default; |
| |
| mojom::HeartbeatResponse SendHeartbeatSync() { |
| base::test::TestFuture<mojom::HeartbeatResponse> test_future; |
| pacemaker_->SendHeartbeat(test_future.GetCallback()); |
| if (!test_future.Wait()) { |
| NOTREACHED_NORETURN(); |
| } |
| return test_future.Get(); |
| } |
| |
| protected: |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| mojo::Remote<mojom::Pacemaker> pacemaker_; |
| std::unique_ptr<HeartbeatTracker> heartbeat_tracker_ = nullptr; |
| }; |
| |
| TEST_F(HeartbeatTrackerTest, DefaultValueAfterCreation) { |
| EXPECT_TRUE(heartbeat_tracker_->IsPacemakerBound()); |
| EXPECT_FALSE(heartbeat_tracker_->IsStopMonitor()); |
| EXPECT_EQ(heartbeat_tracker_->GetFailureCount(), 0); |
| } |
| |
| TEST_F(HeartbeatTrackerTest, PacemakerStopMonitor) { |
| base::test::TestFuture<void> test_future; |
| |
| pacemaker_->StopMonitor(test_future.GetCallback()); |
| if (!test_future.Wait()) { |
| NOTREACHED_NORETURN(); |
| } |
| |
| EXPECT_TRUE(heartbeat_tracker_->IsStopMonitor()); |
| } |
| |
| TEST_F(HeartbeatTrackerTest, PacemakerDisconnect) { |
| pacemaker_.reset(); |
| task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(heartbeat_tracker_->IsPacemakerBound()); |
| } |
| |
| TEST_F(HeartbeatTrackerTest, RebindPacemaker) { |
| EXPECT_TRUE(heartbeat_tracker_->IsPacemakerBound()); |
| |
| pacemaker_.reset(); |
| task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(heartbeat_tracker_->IsPacemakerBound()); |
| |
| heartbeat_tracker_->RebindPacemaker(pacemaker_.BindNewPipeAndPassReceiver()); |
| EXPECT_TRUE(heartbeat_tracker_->IsPacemakerBound()); |
| } |
| |
| TEST_F(HeartbeatTrackerTest, VerifyTimeGap) { |
| mojom::HeartbeatResponse resp = SendHeartbeatSync(); |
| EXPECT_EQ(resp, mojom::HeartbeatResponse::kSuccess); |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| EXPECT_TRUE(heartbeat_tracker_->VerifyTimeGap(base::Time().Now())); |
| |
| // Without sending heartbeat and move on kMinVerificationWindow seconds. |
| task_environment_.FastForwardBy(kMinVerificationWindow); |
| EXPECT_FALSE(heartbeat_tracker_->VerifyTimeGap(base::Time().Now())); |
| } |
| |
| TEST_F(HeartbeatTrackerTest, SetVerificationWindowArgument) { |
| auto argument = mojom::HeartbeatServiceArgument::New(); |
| auto verification_window_seconds = kMinVerificationWindow.InSeconds() + 10; |
| argument->verification_window_seconds = verification_window_seconds; |
| |
| heartbeat_tracker_->SetupArgument(std::move(argument)); |
| mojom::HeartbeatResponse resp = SendHeartbeatSync(); |
| EXPECT_EQ(resp, mojom::HeartbeatResponse::kSuccess); |
| task_environment_.FastForwardBy(base::Seconds(verification_window_seconds)); |
| EXPECT_TRUE(heartbeat_tracker_->VerifyTimeGap(base::Time().Now())); |
| |
| task_environment_.FastForwardBy(base::Seconds(1)); |
| EXPECT_FALSE(heartbeat_tracker_->VerifyTimeGap(base::Time().Now())); |
| } |
| |
| TEST_F(HeartbeatTrackerTest, SetFailureCountActionArgument) { |
| auto action_noop = mojom::Action::New( |
| /*failure_count = */ 2, mojom::ActionType::kNoOperation); |
| auto action_reboot = mojom::Action::New( |
| /*failure_count = */ 3, mojom::ActionType::kNormalReboot); |
| |
| auto argument = mojom::HeartbeatServiceArgument::New(); |
| argument->actions.push_back(action_noop->Clone()); |
| argument->actions.push_back(std::move(action_noop)); |
| argument->actions.push_back(std::move(action_reboot)); |
| |
| heartbeat_tracker_->SetupArgument(std::move(argument)); |
| mojom::HeartbeatResponse resp = SendHeartbeatSync(); |
| EXPECT_EQ(resp, mojom::HeartbeatResponse::kSuccess); |
| task_environment_.FastForwardBy(kMinVerificationWindow + base::Seconds(1)); |
| |
| // VerifyTimeGap increases the failure count to 1. |
| EXPECT_FALSE(heartbeat_tracker_->VerifyTimeGap(base::Time().Now())); |
| auto actions = heartbeat_tracker_->GetFailureCountActions(); |
| std::vector<mojom::ActionType> expected_actions; |
| EXPECT_EQ(actions, expected_actions); |
| |
| // VerifyTimeGap increases the failure count to 2. |
| EXPECT_FALSE(heartbeat_tracker_->VerifyTimeGap(base::Time().Now())); |
| actions = heartbeat_tracker_->GetFailureCountActions(); |
| expected_actions = {mojom::ActionType::kNoOperation, |
| mojom::ActionType::kNoOperation}; |
| EXPECT_EQ(actions, expected_actions); |
| |
| // VerifyTimeGap increases the failure count to 3. |
| EXPECT_FALSE(heartbeat_tracker_->VerifyTimeGap(base::Time().Now())); |
| actions = heartbeat_tracker_->GetFailureCountActions(); |
| expected_actions = {mojom::ActionType::kNormalReboot}; |
| EXPECT_EQ(actions, expected_actions); |
| |
| // VerifyTimeGap increases the failure count to 4. |
| // We should still get the reboot action. |
| EXPECT_FALSE(heartbeat_tracker_->VerifyTimeGap(base::Time().Now())); |
| actions = heartbeat_tracker_->GetFailureCountActions(); |
| expected_actions = {mojom::ActionType::kNormalReboot}; |
| EXPECT_EQ(actions, expected_actions); |
| } |
| |
| TEST_F(HeartbeatTrackerTest, ResetFailureCount) { |
| mojom::HeartbeatResponse resp = SendHeartbeatSync(); |
| EXPECT_EQ(resp, mojom::HeartbeatResponse::kSuccess); |
| task_environment_.FastForwardBy(kMinVerificationWindow + base::Seconds(1)); |
| EXPECT_FALSE(heartbeat_tracker_->VerifyTimeGap(base::Time().Now())); |
| EXPECT_EQ(static_cast<int>(heartbeat_tracker_->GetFailureCount()), 1); |
| |
| resp = SendHeartbeatSync(); |
| EXPECT_EQ(resp, mojom::HeartbeatResponse::kSuccess); |
| EXPECT_TRUE(heartbeat_tracker_->VerifyTimeGap(base::Time().Now())); |
| EXPECT_EQ(static_cast<int>(heartbeat_tracker_->GetFailureCount()), 0); |
| } |
| |
| } // namespace |
| |
| } // namespace heartd |