| // Copyright 2015 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/run_loop.h" |
| #include "base/test/task_environment.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "base/timer/mock_timer.h" |
| #include "gtest/gtest.h" |
| |
| #include "thermald/mock_temperature_sensor.h" |
| #include "thermald/temperature_sensor_monitor.h" |
| |
| using std::string; |
| using std::unique_ptr; |
| using std::vector; |
| using testing::_; |
| using testing::DoAll; |
| using testing::InSequence; |
| using testing::Return; |
| using testing::SetArgPointee; |
| |
| namespace thermald { |
| |
| static const int kTestSamplingPeriodMs = 20; |
| static const int kRunLoopTimeoutMs = 100; |
| static const int kTestTemperature = 42000; |
| |
| class Subscriber { |
| public: |
| explicit Subscriber(std::unique_ptr<base::RunLoop> *run_loop) |
| : cb_(base::Bind(&Subscriber::ProcessTemperatureUpdate, |
| base::Unretained(this))), |
| run_loop_(run_loop) {} |
| |
| ~Subscriber() { |
| if (monitor_ != nullptr) { |
| Unsubscribe(); |
| } |
| } |
| |
| void Subscribe(TemperatureMonitorInterface *monitor) { |
| subscription_ = monitor->Subscribe(cb_); |
| monitor_ = monitor; |
| } |
| |
| void Unsubscribe() { |
| if (monitor_ != nullptr) { |
| #if BASE_VER < 860220 |
| subscription_.reset(); |
| #else |
| subscription_ = {}; |
| #endif |
| monitor_ = nullptr; |
| } |
| } |
| |
| MOCK_METHOD1(DoProcessTemperatureUpdate, void(int temperature)); |
| |
| private: |
| void ProcessTemperatureUpdate(int temperature) { |
| DoProcessTemperatureUpdate(temperature); |
| |
| if (*run_loop_) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, (*run_loop_)->QuitWhenIdleClosure()); |
| } |
| } |
| |
| TemperatureMonitorInterface *monitor_; |
| #if BASE_VER < 860220 |
| std::unique_ptr<base::CallbackList<void(int)>::Subscription> subscription_; |
| #else |
| base::CallbackListSubscription subscription_; |
| #endif |
| base::Callback<void(int)> cb_; |
| // This is no longer memory safe by taking a pointer of std::unique_ptr. |
| // It is only for a leakless base::RunLoop** to track current run_loop in |
| // test. |
| std::unique_ptr<base::RunLoop> *run_loop_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Subscriber); |
| }; |
| |
| class TemperatureSensorMonitorTest : public testing::Test { |
| protected: |
| void SetUp() { |
| sensor_.reset(new MockTemperatureSensor("mock sensor")); |
| monitor_.reset(new TemperatureSensorMonitor(sensor_.get(), |
| test_sampling_period_)); |
| timer_ = new base::MockRepeatingTimer(); |
| monitor_->timer_.reset(timer_); |
| } |
| |
| static void SetUpTestCase() { |
| test_sampling_period_ = |
| base::TimeDelta::FromMilliseconds(kTestSamplingPeriodMs); |
| runloop_timeout_ = |
| base::TimeDelta::FromMilliseconds(kRunLoopTimeoutMs); |
| } |
| |
| void DoRunLoop() { |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| task_environment_.GetMainThreadTaskRunner()->PostDelayedTask( |
| FROM_HERE, run_loop_->QuitClosure(), runloop_timeout_); |
| run_loop_->Run(); |
| } |
| |
| unique_ptr<MockTemperatureSensor> sensor_; |
| unique_ptr<TemperatureSensorMonitor> monitor_; |
| base::MockRepeatingTimer *timer_; |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| std::unique_ptr<base::RunLoop> run_loop_; |
| static base::TimeDelta test_sampling_period_; |
| static base::TimeDelta runloop_timeout_; |
| }; |
| |
| base::TimeDelta TemperatureSensorMonitorTest::test_sampling_period_; |
| base::TimeDelta TemperatureSensorMonitorTest::runloop_timeout_; |
| |
| ACTION_P(SaveArg0ToList, list) { |
| list->push_back(arg0); |
| } |
| |
| // Verify the monitor is idle (no sampling) after it is created. |
| TEST_F(TemperatureSensorMonitorTest, NotSamplingAfterCreation) { |
| ASSERT_FALSE(timer_->IsRunning()); |
| } |
| |
| // Verify that the sampling timer is started upon the first subscription. |
| TEST_F(TemperatureSensorMonitorTest, StartsSamplingWithFirstSubscription) { |
| Subscriber sub(&run_loop_); |
| |
| sub.Subscribe(monitor_.get()); |
| |
| ASSERT_TRUE(timer_->IsRunning()); |
| } |
| |
| // Verify that the current temperature is reported immediately upon the |
| // first subscription. |
| TEST_F(TemperatureSensorMonitorTest, NotifiesFirstSubscriberImmediately) { |
| Subscriber sub(&run_loop_); |
| |
| EXPECT_CALL(*sensor_, ReadTemperature(_)) |
| .WillOnce(DoAll(SetArgPointee<0>(kTestTemperature), Return(true))); |
| |
| sub.Subscribe(monitor_.get()); |
| |
| EXPECT_CALL(sub, DoProcessTemperatureUpdate(kTestTemperature)); |
| |
| DoRunLoop(); |
| } |
| |
| // Verify that a re-subscriber is always notified about the current temperature, |
| // even if the temperature hasn't changed since the last notification before |
| // unsubscribing. |
| TEST_F(TemperatureSensorMonitorTest, NotifiesOnResubscription) { |
| Subscriber sub(&run_loop_); |
| |
| EXPECT_CALL(*sensor_, ReadTemperature(_)) |
| .WillRepeatedly(DoAll(SetArgPointee<0>(kTestTemperature), |
| Return(true))); |
| |
| EXPECT_CALL(sub, DoProcessTemperatureUpdate(kTestTemperature)).Times(2); |
| |
| sub.Subscribe(monitor_.get()); |
| DoRunLoop(); |
| sub.Unsubscribe(); |
| |
| sub.Subscribe(monitor_.get()); |
| DoRunLoop(); |
| } |
| |
| // Verify the sampling of the sensor continues after a subscriber cancels its |
| // subscription as long as at least one subscription is active. |
| TEST_F(TemperatureSensorMonitorTest, KeepsSamplingWithSubscriptions) { |
| Subscriber sub1(&run_loop_), sub2(&run_loop_); |
| |
| sub1.Subscribe(monitor_.get()); |
| sub2.Subscribe(monitor_.get()); |
| |
| sub1.Unsubscribe(); |
| |
| ASSERT_TRUE(timer_->IsRunning()); |
| } |
| |
| // Verify the sampling is stopped when the last subscription is canceled |
| // (test case with a single subscriber). |
| TEST_F(TemperatureSensorMonitorTest, |
| StopsSamplingWithoutSubscriptions_SingleSubscriber) { |
| Subscriber sub(&run_loop_); |
| |
| sub.Subscribe(monitor_.get()); |
| sub.Unsubscribe(); |
| |
| ASSERT_FALSE(timer_->IsRunning()); |
| } |
| |
| // Verify the sampling is stopped when the last subscription is canceled |
| // (test case with multiple subscribers). |
| TEST_F(TemperatureSensorMonitorTest, |
| StopsSamplingWithoutSubscriptions_MultipleSubscribers) { |
| Subscriber sub1(&run_loop_), sub2(&run_loop_); |
| |
| sub1.Subscribe(monitor_.get()); |
| sub2.Subscribe(monitor_.get()); |
| sub1.Unsubscribe(); |
| sub2.Unsubscribe(); |
| |
| ASSERT_FALSE(timer_->IsRunning()); |
| } |
| |
| // Verify that the cancellation of a subscription doesn't affect the |
| // remaining subscribers. |
| TEST_F(TemperatureSensorMonitorTest, |
| KeepsNotifiyingRemainingSubscribersAfterUnsubscription) { |
| Subscriber sub1(&run_loop_), sub2(&run_loop_); |
| vector<int> sensor_values = {23, 42, 67, 19, 7}; |
| size_t i; |
| |
| { |
| InSequence s; |
| |
| for (auto &value : sensor_values) { |
| EXPECT_CALL(*sensor_, ReadTemperature(_)) |
| .WillOnce(DoAll(SetArgPointee<0>(value), Return(true))) |
| .RetiresOnSaturation(); |
| } |
| } |
| |
| sub1.Subscribe(monitor_.get()); |
| sub2.Subscribe(monitor_.get()); |
| |
| EXPECT_CALL(sub1, DoProcessTemperatureUpdate(_)).Times(sensor_values.size()); |
| EXPECT_CALL(sub2, DoProcessTemperatureUpdate(_)).Times(3); |
| |
| for (i = 0; i < 3; i++) { |
| timer_->Fire(); |
| } |
| |
| sub2.Unsubscribe(); |
| |
| for (i = 3; i < sensor_values.size(); i++) { |
| timer_->Fire(); |
| } |
| } |
| |
| // Verify that subscribers are notified about the values read from the |
| // temperature sensor. |
| TEST_F(TemperatureSensorMonitorTest, ReportsTemperatureToSubscribers) { |
| Subscriber sub1(&run_loop_), sub2(&run_loop_); |
| vector<int> sensor_values = {23, 42, 67, 19, 7}; |
| vector<int> reported_values1, reported_values2; |
| |
| { |
| InSequence s; |
| |
| for (auto &value : sensor_values) { |
| EXPECT_CALL(*sensor_, ReadTemperature(_)) |
| .WillOnce(DoAll(SetArgPointee<0>(value), Return(true))) |
| .RetiresOnSaturation(); |
| } |
| } |
| |
| EXPECT_CALL(sub1, DoProcessTemperatureUpdate(_)) |
| .WillRepeatedly(SaveArg0ToList(&reported_values1)); |
| EXPECT_CALL(sub2, DoProcessTemperatureUpdate(_)) |
| .WillRepeatedly(SaveArg0ToList(&reported_values2)); |
| sub1.Subscribe(monitor_.get()); |
| sub2.Subscribe(monitor_.get()); |
| |
| for (size_t i = 0; i < sensor_values.size(); i++) { |
| timer_->Fire(); |
| } |
| |
| EXPECT_EQ(sensor_values, reported_values1); |
| EXPECT_EQ(sensor_values, reported_values2); |
| } |
| |
| // Verify that subscribers are only notified on changes and not every |
| // time the sensor is read. |
| TEST_F(TemperatureSensorMonitorTest, ReportsOnlyTemperatureChanges) { |
| Subscriber sub(&run_loop_); |
| vector<int> sensor_values = {23, 42, 67, 67, 67, 19, 7, 7, 13}; |
| vector<int> expected_values = {23, 42, 67, 19, 7, 13}; |
| vector<int> reported_values; |
| |
| { |
| InSequence s; |
| |
| for (auto &value : sensor_values) { |
| EXPECT_CALL(*sensor_, ReadTemperature(_)) |
| .WillOnce(DoAll(SetArgPointee<0>(value), Return(true))) |
| .RetiresOnSaturation(); |
| } |
| } |
| |
| EXPECT_CALL(sub, DoProcessTemperatureUpdate(_)) |
| .WillRepeatedly(SaveArg0ToList(&reported_values)); |
| sub.Subscribe(monitor_.get()); |
| |
| for (size_t i = 0; i < sensor_values.size(); i++) { |
| timer_->Fire(); |
| } |
| |
| EXPECT_EQ(expected_values, reported_values); |
| } |
| |
| // Verify that a new subscriber is always notified about the current |
| // temperature, even if the temperature hasn't changed since the previous |
| // reading. |
| TEST_F(TemperatureSensorMonitorTest, ReportsCurrentTemperatureToNewSubscriber) { |
| Subscriber sub1(&run_loop_), sub2(&run_loop_); |
| |
| EXPECT_CALL(*sensor_, ReadTemperature(_)) |
| .WillRepeatedly(DoAll(SetArgPointee<0>(kTestTemperature), |
| Return(true))); |
| |
| EXPECT_CALL(sub1, DoProcessTemperatureUpdate(kTestTemperature)); |
| sub1.Subscribe(monitor_.get()); |
| timer_->Fire(); |
| |
| EXPECT_CALL(sub2, DoProcessTemperatureUpdate(kTestTemperature)); |
| sub2.Subscribe(monitor_.get()); |
| DoRunLoop(); |
| } |
| |
| // Verify that a new subscriber receives only one notification about the |
| // current temperature. Background: Upon subscription a task is scheduled |
| // to report the current temperature as soon as possible. The purpose of this |
| // test is to verify that a constant temperature isn't reported twice, once by |
| // the task for the initial reporting and then again by the periodic timer. |
| TEST_F(TemperatureSensorMonitorTest, |
| ReportsCurrentTemperatureOnlyOnceToNewSubscriber) { |
| Subscriber sub1(&run_loop_), sub2(&run_loop_); |
| |
| EXPECT_CALL(*sensor_, ReadTemperature(_)) |
| .WillRepeatedly(DoAll(SetArgPointee<0>(kTestTemperature), |
| Return(true))); |
| |
| EXPECT_CALL(sub1, DoProcessTemperatureUpdate(kTestTemperature)); |
| sub1.Subscribe(monitor_.get()); |
| timer_->Fire(); |
| |
| EXPECT_CALL(sub2, DoProcessTemperatureUpdate(kTestTemperature)); |
| sub2.Subscribe(monitor_.get()); |
| DoRunLoop(); |
| timer_->Fire(); |
| } |
| |
| // Verify that the temperature at subscription time is not reported to a |
| // new subscriber if the periodic sampling kicked in before the initial |
| // reporting and the temperature has changed. |
| TEST_F(TemperatureSensorMonitorTest, |
| DoesNotReportOutdatedValueToNewSubscriber) { |
| Subscriber sub1(&run_loop_), sub2(&run_loop_); |
| const int kNewTemperature = kTestTemperature + 1000; |
| |
| { |
| InSequence s; |
| |
| EXPECT_CALL(*sensor_, ReadTemperature(_)) |
| .WillOnce(DoAll(SetArgPointee<0>(kTestTemperature), Return(true))); |
| EXPECT_CALL(sub1, DoProcessTemperatureUpdate(kTestTemperature)); |
| |
| EXPECT_CALL(*sensor_, ReadTemperature(_)) |
| .WillOnce(DoAll(SetArgPointee<0>(kNewTemperature), Return(true))); |
| EXPECT_CALL(sub1, DoProcessTemperatureUpdate(kNewTemperature)); |
| } |
| |
| sub1.Subscribe(monitor_.get()); |
| DoRunLoop(); |
| |
| EXPECT_CALL(sub2, DoProcessTemperatureUpdate(kNewTemperature)); |
| // Schedules a task to report the current/latest temperature. |
| sub2.Subscribe(monitor_.get()); |
| // Timer fires before the initial reporting tasks runs and the new temperature |
| // is reported by the sensor. |
| timer_->Fire(); |
| |
| // Allow the initial reporting task to run, which should not report the old |
| // temperature. |
| DoRunLoop(); |
| } |
| |
| } // namespace thermald |