| // 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 |