blob: e0178ba3c120aabef1ef05fecb00a6d59cc171d3 [file] [log] [blame]
// Copyright 2021 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 <gtest/gtest.h>
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/check.h>
#include <base/check_op.h>
#include <base/rand_util.h>
#include <base/run_loop.h>
#include <base/stl_util.h>
#include <base/strings/stringprintf.h>
#include <base/test/task_environment.h>
#include <libmems/common_types.h>
#include <libmems/test_fakes.h>
#include <mojo/public/cpp/bindings/receiver_set.h>
#include "iioservice/daemon/common_types.h"
#include "iioservice/daemon/samples_handler_fusion.h"
#include "iioservice/daemon/sensor_metrics_mock.h"
#include "iioservice/daemon/test_fakes.h"
#include "iioservice/mojo/sensor.mojom.h"
namespace iioservice {
namespace {
constexpr double kMinFrequency = 20.0;
constexpr double kMaxFrequency = 40.0;
constexpr double kFooFrequency = 20.0;
constexpr int kNumFailures = 10;
double FixFrequency(double frequency) {
if (frequency < libmems::kFrequencyEpsilon)
return 0.0;
if (frequency > kMaxFrequency)
return kMaxFrequency;
return frequency;
}
double FixFrequencyWithMin(double frequency) {
if (frequency < libmems::kFrequencyEpsilon)
return 0.0;
if (frequency < kMinFrequency)
return kMinFrequency;
if (frequency > kMaxFrequency)
return kMaxFrequency;
return frequency;
}
} // namespace
class SamplesHandlerFusionTestBase
: public cros::mojom::SensorDeviceSamplesObserver {
public:
mojo::PendingRemote<cros::mojom::SensorDeviceSamplesObserver> GetRemote() {
mojo::PendingRemote<cros::mojom::SensorDeviceSamplesObserver> remote;
receiver_set_.Add(this, remote.InitWithNewPipeAndPassReceiver());
return remote;
}
// cros::mojom::SensorDeviceSamplesObserver overrides:
void OnSampleUpdated(const libmems::IioDevice::IioSample& sample) override {
CHECK(
task_environment_.GetMainThreadTaskRunner()->BelongsToCurrentThread());
}
void OnErrorOccurred(cros::mojom::ObserverErrorType type) override {
CHECK(
task_environment_.GetMainThreadTaskRunner()->BelongsToCurrentThread());
CHECK_EQ(type, cros::mojom::ObserverErrorType::FREQUENCY_INVALID);
}
protected:
void SetUpBase() {
SensorMetricsMock::InitializeForTesting();
handler_ = std::make_unique<SamplesHandlerFusion>(
task_environment_.GetMainThreadTaskRunner(), GetGravityChannels(),
base::BindRepeating(
&SamplesHandlerFusionTestBase::UpdateRequestedFrequency,
base::Unretained(this)));
EXPECT_TRUE(handler_);
}
void TearDownBase() {
handler_.reset();
observers_.clear();
base::RunLoop().RunUntilIdle();
// ClientData should be valid until |handler_| is destructed.
clients_data_.clear();
SensorMetrics::Shutdown();
}
void UpdateRequestedFrequency(double frequency) {
frequency_ = FixFrequencyWithMin(frequency);
// |handler_| might not exist in the d'tor.
if (handler_)
handler_->SetDevFrequency(frequency_);
}
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME,
base::test::TaskEnvironment::MainThreadType::IO};
double frequency_ = 0.0;
std::vector<ClientData> clients_data_;
std::vector<std::unique_ptr<fakes::FakeSamplesObserver>> observers_;
std::unique_ptr<SamplesHandlerFusion> handler_;
mojo::ReceiverSet<cros::mojom::SensorDeviceSamplesObserver> receiver_set_;
};
class SamplesHandlerFusionTest : public ::testing::Test,
public SamplesHandlerFusionTestBase {
protected:
void SetUp() override { SetUpBase(); }
void TearDown() override { TearDownBase(); }
};
TEST_F(SamplesHandlerFusionTest, AddClientAndRemoveClient) {
clients_data_.emplace_back(ClientData(0));
ClientData& client_data = clients_data_[0];
client_data.frequency = kFooFrequency;
client_data.enabled_chn_indices.emplace(3); // timestamp
handler_->AddClient(&client_data, GetRemote());
base::RunLoop run_loop;
fakes::FakeObserver observer(run_loop.QuitClosure());
handler_->AddClient(&client_data, observer.GetRemote());
// Wait until |Observer| is disconnected.
run_loop.Run();
handler_->RemoveClient(&client_data);
// RemoveClient can be called multiple times.
handler_->RemoveClient(&client_data);
}
TEST_F(SamplesHandlerFusionTest, BadDeviceWithNoSamples) {
handler_->Invalidate();
std::vector<double> freqs = {5.0, 0.0, 10.0, 100.0};
clients_data_.reserve(freqs.size());
for (size_t i = 0; i < freqs.size(); ++i) {
clients_data_.emplace_back(ClientData(i));
ClientData& client_data = clients_data_[i];
// At least one channel enabled
client_data.enabled_chn_indices.emplace(3); // timestamp
client_data.frequency = freqs[i];
client_data.timeout = 0;
std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> failures;
failures.insert(
std::make_pair(0, cros::mojom::ObserverErrorType::FREQUENCY_INVALID));
// To match the check in |fakes::FakeSamplesObserver| for FREQUENCY_INVALID,
// pass frequencies as zeros.
auto fake_observer = fakes::FakeSamplesObserver::Create(
nullptr, std::move(failures), 0.0, 0.0, kFooFrequency, kFooFrequency);
handler_->AddClient(&client_data, fake_observer->GetRemote());
observers_.emplace_back(std::move(fake_observer));
}
// Wait until all observers receive all samples
base::RunLoop().RunUntilIdle();
for (const auto& observer : observers_)
EXPECT_TRUE(observer->NoRemainingFailures());
// Remove clients
for (auto& client_data : clients_data_)
handler_->RemoveClient(&client_data);
}
class SamplesHandlerFusionTestWithParam
: public ::testing::TestWithParam<std::vector<std::pair<double, double>>>,
public SamplesHandlerFusionTestBase {
protected:
void SetUp() override { SetUpBase(); }
void TearDown() override { TearDownBase(); }
void SetUpAccel() {
accel_ = std::make_unique<libmems::fakes::FakeIioDevice>(
nullptr, fakes::kAccelDeviceName, fakes::kAccelDeviceId);
for (int i = 0; i < base::size(libmems::fakes::kFakeAccelChns); ++i) {
accel_->AddChannel(std::make_unique<libmems::fakes::FakeIioChannel>(
libmems::fakes::kFakeAccelChns[i], true));
}
EXPECT_TRUE(accel_->WriteDoubleAttribute(libmems::kSamplingFrequencyAttr,
kFooFrequency));
accel_->CreateBuffer();
}
void OnSampleAvailable(const base::flat_map<int32_t, int64_t>& sample) {
if (handler_)
handler_->OnSampleAvailableOnThread(sample);
}
void ReadSamples(int num) {
for (int i = 0; i < num; ++i) {
auto sample_opt = accel_->ReadSample();
if (!sample_opt.has_value()) {
--i;
// Pass an invalid sample to trigger a READ_FAILED.
OnSampleAvailable(base::flat_map<int32_t, int64_t>());
continue;
}
OnSampleAvailable(sample_opt.value());
}
}
std::unique_ptr<libmems::fakes::FakeIioDevice> accel_;
};
// Add clients with the first frequencies set, update clients with the second
// frequencies, and remove clients. Clients' frequencies and the sample
// handler's |sampling_frequency_| are checked after each modification.
TEST_P(SamplesHandlerFusionTestWithParam, UpdateFrequency) {
clients_data_.reserve(GetParam().size());
std::multiset<double> frequencies;
// Add clients
for (size_t i = 0; i < GetParam().size(); ++i) {
clients_data_.emplace_back(ClientData(i));
ClientData& client_data = clients_data_[i];
// At least one channel enabled
client_data.enabled_chn_indices.emplace(3); // timestamp
client_data.timeout = 0;
client_data.frequency = GetParam()[i].first;
handler_->AddClient(&client_data, GetRemote());
frequencies.emplace(FixFrequency(client_data.frequency));
EXPECT_EQ(frequency_, FixFrequencyWithMin(*frequencies.rbegin()));
}
// Update clients' frequencies
for (size_t i = 0; i < GetParam().size(); ++i) {
ClientData& client_data = clients_data_[i];
double new_freq = GetParam()[i].second;
handler_->UpdateFrequency(&client_data, new_freq);
auto it = frequencies.find(FixFrequency(GetParam()[i].first));
EXPECT_TRUE(it != frequencies.end());
frequencies.erase(it);
frequencies.emplace(FixFrequency(new_freq));
EXPECT_EQ(frequency_, FixFrequencyWithMin(*frequencies.rbegin()));
}
// Remove clients
for (size_t i = 0; i < GetParam().size(); ++i) {
ClientData& client_data = clients_data_[i];
handler_->RemoveClient(&client_data);
auto it = frequencies.find(FixFrequency(GetParam()[i].second));
EXPECT_TRUE(it != frequencies.end());
frequencies.erase(it);
EXPECT_EQ(frequency_, i == GetParam().size() - 1
? 0.0
: FixFrequencyWithMin(*frequencies.rbegin()));
}
}
// Add all clients into the sample handler, read the first |kPauseIndex|
// samples and pause reading, update clients' frequencies and enable accel_y
// channel, and read the rest samples. All samples are checked when received by
// observers.
TEST_P(SamplesHandlerFusionTestWithParam, ReadSamplesWithFrequency) {
SetUpAccel();
std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> rf_failures;
for (int i = 0; i < kNumFailures; ++i) {
int k = base::RandInt(0, base::size(libmems::fakes::kFakeAccelSamples) - 1);
accel_->AddFailedReadAtKthSample(k);
rf_failures.insert(
std::make_pair(k, cros::mojom::ObserverErrorType::READ_FAILED));
}
clients_data_.reserve(GetParam().size());
double max_freq = -1, max_freq2 = -1;
for (size_t i = 0; i < GetParam().size(); ++i) {
max_freq = std::max(max_freq, GetParam()[i].first);
max_freq2 = std::max(max_freq2, GetParam()[i].second);
}
max_freq = FixFrequencyWithMin(max_freq);
max_freq2 = FixFrequencyWithMin(max_freq2);
for (size_t i = 0; i < GetParam().size(); ++i) {
clients_data_.emplace_back(ClientData(i));
ClientData& client_data = clients_data_[i];
client_data.enabled_chn_indices.emplace(0); // gravity_x
client_data.enabled_chn_indices.emplace(2); // gravity_z
client_data.enabled_chn_indices.emplace(3); // timestamp
client_data.frequency = GetParam()[i].first;
auto failures = rf_failures;
if (GetParam()[i].first == 0.0) {
while (!failures.empty() && failures.begin()->first < fakes::kPauseIndex)
failures.erase(failures.begin());
failures.insert(
std::make_pair(0, cros::mojom::ObserverErrorType::FREQUENCY_INVALID));
}
// The fake observer needs |max_freq| and |max_freq2| to calculate the
// correct values of samples
auto fake_observer = fakes::FakeSamplesObserver::Create(
accel_.get(), std::move(failures), FixFrequency(GetParam()[i].first),
FixFrequency(GetParam()[i].second), max_freq, max_freq2);
handler_->AddClient(&client_data, fake_observer->GetRemote());
observers_.emplace_back(std::move(fake_observer));
}
EXPECT_EQ(frequency_, max_freq);
// Read |fakes::kPauseIndex| samples.
ReadSamples(fakes::kPauseIndex);
for (int i = 0; i < clients_data_.size(); ++i) {
ClientData& client_data = clients_data_[i];
// Update to the second frequency
handler_->UpdateFrequency(&client_data, GetParam()[i].second);
client_data.enabled_chn_indices.emplace(1); // gravity_y
}
// Read the rest samples.
ReadSamples(base::size(libmems::fakes::kFakeAccelSamples) -
fakes::kPauseIndex);
EXPECT_EQ(frequency_, max_freq2);
// Wait until all observers receive all samples
base::RunLoop().RunUntilIdle();
for (const auto& observer : observers_)
EXPECT_TRUE(observer->FinishedObserving());
// Remove clients
for (auto& client_data : clients_data_)
handler_->RemoveClient(&client_data);
}
INSTANTIATE_TEST_SUITE_P(
SamplesHandlerFusionTestWithParamRun,
SamplesHandlerFusionTestWithParam,
::testing::Values(std::vector<std::pair<double, double>>(3, {10.0, 10.0}),
std::vector<std::pair<double, double>>{
{20.0, 50.0}, {10.0, 10.0}, {2.0, 3.0}},
std::vector<std::pair<double, double>>{
{10.0, 20.0}, {20.0, 30.0}, {0.0, 0.0}},
std::vector<std::pair<double, double>>{
{80.0, 50.0}, {10.0, 10.0}, {2.0, 3.0}},
std::vector<std::pair<double, double>>{
{10.0, 40.0}, {0.0, 20.0}, {2.0, 3.0}, {40.0, 10.0}},
std::vector<std::pair<double, double>>{
{2.0, 10.0}, {10.0, 30.0}, {80.0, 0.0}},
std::vector<std::pair<double, double>>{
{0.0, 10.0}, {10.0, 30.0}, {80.0, 60.0}},
std::vector<std::pair<double, double>>{
{2.0, 10.0}, {50.0, 30.0}, {80.0, 60.0}},
std::vector<std::pair<double, double>>{{20.0, 30.0},
{10.0, 10.0}}));
} // namespace iioservice