blob: 9779817b80368b58e3cec3a729085cbfb74b0e6c [file] [log] [blame]
// 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/message_loop/message_loop.h"
#include "base/run_loop.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::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) {
subscription_.reset();
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_;
std::unique_ptr<base::CallbackList<void(int)>::Subscription> subscription_;
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>();
main_loop_.task_runner()->PostDelayedTask(
FROM_HERE, run_loop_->QuitClosure(), runloop_timeout_);
run_loop_->Run();
}
unique_ptr<MockTemperatureSensor> sensor_;
unique_ptr<TemperatureSensorMonitor> monitor_;
base::MockRepeatingTimer *timer_;
base::MessageLoop main_loop_;
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