blob: ea8082683528503ffc19da4eedeb274b6d62a62f [file] [log] [blame]
// Copyright 2020 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 <limits>
#include <queue>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include <base/rand_util.h>
#include <base/run_loop.h>
#include <base/task/single_thread_task_executor.h>
#include <base/threading/thread.h>
#include <mojo/core/embedder/scoped_ipc_support.h>
#include <mojo/public/cpp/bindings/receiver_set.h>
#include <libmems/common_types.h>
#include <libmems/test_fakes.h>
#include "iioservice/daemon/samples_handler.h"
#include "iioservice/daemon/test_fakes.h"
#include "iioservice/include/constants.h"
#include "mojo/sensor.mojom.h"
namespace iioservice {
namespace {
constexpr double kMinFrequency = 1.25;
constexpr double kMaxFrequency = 40.0;
constexpr int kFakeTimeoutInMilliseconds = 10;
constexpr double kFooFrequency = 20.0;
constexpr int kNumFailures = 10;
double FixFrequency(double frequency) {
if (frequency < kFrequencyEpsilon)
return 0.0;
if (frequency < kMinFrequency)
return kMinFrequency;
if (frequency > kMaxFrequency)
return kMaxFrequency;
return frequency;
}
class SamplesHandlerTestOnMinMaxFrequencyWithParam
: public ::testing::TestWithParam<
std::tuple<std::string, bool, double, double>> {
protected:
void SetUp() override {
device_ = std::make_unique<libmems::fakes::FakeIioDevice>(
nullptr, fakes::kAccelDeviceName, fakes::kAccelDeviceId);
EXPECT_TRUE(device_->WriteStringAttribute(kSamplingFrequencyAvailable,
std::get<0>(GetParam())));
result_ = SamplesHandler::GetDevMinMaxFrequency(device_.get(), &min_freq_,
&max_freq_);
}
std::unique_ptr<libmems::fakes::FakeIioDevice> device_;
bool result_;
double min_freq_ = -1;
double max_freq_ = -1;
};
TEST_P(SamplesHandlerTestOnMinMaxFrequencyWithParam, ParseMinMaxFrequency) {
EXPECT_EQ(result_, std::get<1>(GetParam()));
if (!result_)
return;
EXPECT_EQ(min_freq_, std::get<2>(GetParam()));
EXPECT_EQ(max_freq_, std::get<3>(GetParam()));
}
INSTANTIATE_TEST_SUITE_P(
SamplesHandlerTestOnMinMaxFrequencyWithParamRun,
SamplesHandlerTestOnMinMaxFrequencyWithParam,
::testing::Values(
std::make_tuple(" ", false, 0.0, 0.0),
std::make_tuple(" 0abc ", false, 0.0, 0.0),
std::make_tuple(" 0.0001 ", false, 0.0, 0.0),
std::make_tuple("0.5 ", true, 0.5, 0.5),
std::make_tuple(" 1000 ", true, 1000.0, 1000.0),
std::make_tuple("1.0 100.0 ", true, 1.0, 100.0),
std::make_tuple("1.0 10.0 100.0 ", true, 1.0, 100.0),
std::make_tuple("1.0 a b c 100.0 ", true, 1.0, 100.0),
std::make_tuple("0.0 a b c 100.0 ", false, 0.0, 0.0),
std::make_tuple("0.0 1.0 100.0 ", true, 1.0, 100.0),
std::make_tuple("0.0 2.0 a b c 100.0 ", true, 2.0, 100.0)));
class SamplesHandlerTestBase {
protected:
void OnSampleUpdatedCallback(mojo::ReceiverId id,
libmems::IioDevice::IioSample sample) {
CHECK(ipc_thread_->task_runner()->BelongsToCurrentThread());
CHECK_LT(id, observers_.size());
observers_[id]->OnSampleUpdated(std::move(sample));
}
void OnErrorOccurredCallback(mojo::ReceiverId id,
cros::mojom::ObserverErrorType type) {
CHECK(ipc_thread_->task_runner()->BelongsToCurrentThread());
if (id >= observers_.size()) {
CHECK_EQ(type, cros::mojom::ObserverErrorType::FREQUENCY_INVALID);
return;
}
observers_[id]->OnErrorOccurred(type);
}
void SetUpBase() {
device_ = std::make_unique<libmems::fakes::FakeIioDevice>(
nullptr, fakes::kAccelDeviceName, fakes::kAccelDeviceId);
EXPECT_TRUE(device_->WriteStringAttribute(
kSamplingFrequencyAvailable, fakes::kFakeSamplingFrequencyAvailable));
for (int i = 0; i < base::size(libmems::fakes::kFakeAccelChns); ++i) {
device_->AddChannel(std::make_unique<libmems::fakes::FakeIioChannel>(
libmems::fakes::kFakeAccelChns[i], true));
}
EXPECT_TRUE(
device_->WriteDoubleAttribute(libmems::kSamplingFrequencyAttr, 0.0));
ipc_thread_ = std::make_unique<base::Thread>("IPCThread");
EXPECT_TRUE(ipc_thread_->StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0)));
sample_thread_ = std::make_unique<base::Thread>("SampleThread");
EXPECT_TRUE(sample_thread_->StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0)));
handler_ = fakes::FakeSamplesHandler::CreateWithFifo(
ipc_thread_->task_runner(), sample_thread_->task_runner(),
device_.get(),
base::BindRepeating(&SamplesHandlerTestBase::OnSampleUpdatedCallback,
base::Unretained(this)),
base::BindRepeating(&SamplesHandlerTestBase::OnErrorOccurredCallback,
base::Unretained(this)));
EXPECT_TRUE(handler_);
}
void TearDownBase() {
handler_.reset();
sample_thread_->Stop();
EXPECT_EQ(device_->ReadDoubleAttribute(libmems::kSamplingFrequencyAttr)
.value_or(-1),
0.0);
observers_.clear();
ipc_thread_->Stop();
}
std::unique_ptr<libmems::fakes::FakeIioDevice> device_;
std::unique_ptr<base::Thread> ipc_thread_;
std::unique_ptr<base::Thread> sample_thread_;
fakes::FakeSamplesHandler::ScopedFakeSamplesHandler handler_ = {
nullptr, SamplesHandler::SamplesHandlerDeleter};
std::vector<ClientData> clients_data_;
std::vector<fakes::FakeSamplesObserver::ScopedFakeSamplesObserver> observers_;
};
class SamplesHandlerTest : public ::testing::Test,
public SamplesHandlerTestBase {
protected:
void SetUp() override { SetUpBase(); }
void TearDown() override { TearDownBase(); }
};
// Add clients with only timestamp channel enabled, enable all other channels,
// and disable all channels except for accel_x. Enabled channels are checked
// after each modification.
TEST_F(SamplesHandlerTest, UpdateChannelsEnabled) {
// No samples in this test
device_->SetPauseCallbackAtKthSamples(0, base::BindOnce([]() {}));
std::vector<double> freqs = {0.0, 10.0};
clients_data_.resize(freqs.size());
for (size_t i = 0; i < freqs.size(); ++i) {
ClientData& client_data = clients_data_[i];
client_data.id = i;
client_data.iio_device = device_.get();
client_data.enabled_chn_indices.emplace(3); // timestamp
client_data.timeout = 0;
client_data.frequency = freqs[i];
handler_->AddClient(&client_data);
handler_->UpdateChannelsEnabled(
&client_data, std::vector<int32_t>{1, 0, 2}, true,
base::BindOnce([](const std::vector<int32_t>& failed_indices) {
EXPECT_TRUE(failed_indices.empty());
}));
handler_->GetChannelsEnabled(
&client_data, std::vector<int32_t>{1, 0, 2, 3},
base::BindOnce([](const std::vector<bool>& enabled) {
EXPECT_EQ(enabled.size(), 4);
EXPECT_TRUE(enabled[0] && enabled[1] && enabled[2] && enabled[3]);
}));
handler_->UpdateChannelsEnabled(
&client_data, std::vector<int32_t>{2, 1, 3}, false,
base::BindOnce(
[](ClientData* client_data, int32_t chn_index,
const std::vector<int32_t>& failed_indices) {
EXPECT_EQ(client_data->enabled_chn_indices.size(), 1);
EXPECT_EQ(*client_data->enabled_chn_indices.begin(), chn_index);
EXPECT_TRUE(failed_indices.empty());
},
&client_data, 0));
}
// Remove clients
for (auto& client_data : clients_data_)
handler_->RemoveClient(&client_data);
}
TEST_F(SamplesHandlerTest, BadDeviceWithNoSamples) {
base::SingleThreadTaskExecutor task_executor(base::MessagePumpType::IO);
device_->DisableFd();
std::vector<double> freqs = {5.0, 0.0, 10.0, 100.0};
clients_data_.resize(freqs.size());
std::vector<std::unique_ptr<base::RunLoop>> loops;
std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> rt_failures;
rt_failures.insert(
std::make_pair(0, cros::mojom::ObserverErrorType::READ_TIMEOUT));
size_t fd_failed_cnt = 0;
for (size_t i = 0; i < freqs.size(); ++i) {
if (freqs[i] > 0.0)
++fd_failed_cnt;
}
for (size_t i = 0; i < freqs.size(); ++i) {
ClientData& client_data = clients_data_[i];
std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
client_data.id = i;
client_data.iio_device = device_.get();
client_data.enabled_chn_indices.emplace(3); // timestamp
client_data.frequency = freqs[i];
client_data.timeout = kFakeTimeoutInMilliseconds;
auto failures = rt_failures;
if (freqs[i] == 0.0) {
failures.insert(
std::make_pair(0, cros::mojom::ObserverErrorType::FREQUENCY_INVALID));
} else {
for (size_t j = 0; j < fd_failed_cnt; ++j) {
failures.insert(
std::make_pair(0, cros::mojom::ObserverErrorType::GET_FD_FAILED));
}
--fd_failed_cnt;
}
// Don't care about frequencies as there would be no samples.
auto fake_observer = fakes::FakeSamplesObserver::Create(
ipc_thread_->task_runner(), run_loop->QuitClosure(), device_.get(),
std::move(failures), kFooFrequency, kFooFrequency, kFooFrequency,
kFooFrequency);
handler_->AddClient(&client_data);
loops.emplace_back(std::move(run_loop));
observers_.emplace_back(std::move(fake_observer));
}
// Wait until all observers receive the READ_TIMEOUT error
for (auto& run_loop : loops)
run_loop->Run();
// Remove clients
for (auto& client_data : clients_data_)
handler_->RemoveClient(&client_data);
}
class SamplesHandlerTestWithParam
: public ::testing::TestWithParam<std::vector<std::pair<double, double>>>,
public SamplesHandlerTestBase {
protected:
void SetUp() override { SetUpBase(); }
void TearDown() override { TearDownBase(); }
};
// Add clients with the first frequencies set, update clients with the second
// frequencies, and remove clients. Clients' frequencies and the sample
// handler's |max_frequency_| are checked after each modification.
TEST_P(SamplesHandlerTestWithParam, UpdateFrequency) {
// No samples in this test
device_->SetPauseCallbackAtKthSamples(0, base::BindOnce([]() {}));
clients_data_.resize(GetParam().size());
std::multiset<double> frequencies;
// Add clients
for (size_t i = 0; i < GetParam().size(); ++i) {
ClientData& client_data = clients_data_[i];
client_data.id = i;
client_data.iio_device = device_.get();
// 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);
frequencies.emplace(FixFrequency(client_data.frequency));
handler_->CheckRequestedFrequency(*frequencies.rbegin());
}
base::SingleThreadTaskExecutor task_executor(base::MessagePumpType::IO);
// 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,
base::BindOnce(
[](ClientData* client_data, double fixed_new_freq,
double result_freq) {
EXPECT_EQ(client_data->frequency, fixed_new_freq);
EXPECT_EQ(result_freq, fixed_new_freq);
},
&client_data, FixFrequency(new_freq)));
auto it = frequencies.find(FixFrequency(GetParam()[i].first));
EXPECT_TRUE(it != frequencies.end());
frequencies.erase(it);
frequencies.emplace(FixFrequency(new_freq));
handler_->CheckRequestedFrequency(*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);
handler_->CheckRequestedFrequency(
i == GetParam().size() - 1 ? 0.0 : *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(SamplesHandlerTestWithParam, ReadSamplesWithFrequency) {
base::SingleThreadTaskExecutor task_executor(base::MessagePumpType::IO);
base::RunLoop pause_loop;
// Set the pause in the beginning to prevent reading samples before all
// clients added.
device_->SetPauseCallbackAtKthSamples(
0, base::BindOnce([](base::Closure quit_closure) { quit_closure.Run(); },
pause_loop.QuitClosure()));
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);
device_->AddFailedReadAtKthSample(k);
rf_failures.insert(
std::make_pair(k, cros::mojom::ObserverErrorType::READ_FAILED));
}
clients_data_.resize(GetParam().size());
std::vector<std::unique_ptr<base::RunLoop>> loops;
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 = FixFrequency(max_freq);
max_freq2 = FixFrequency(max_freq2);
for (size_t i = 0; i < GetParam().size(); ++i) {
ClientData& client_data = clients_data_[i];
std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
client_data.id = i;
client_data.iio_device = device_.get();
client_data.enabled_chn_indices.emplace(0); // accel_x
client_data.enabled_chn_indices.emplace(2); // accel_y
client_data.enabled_chn_indices.emplace(3); // timestamp
client_data.frequency = GetParam()[i].first;
auto failures = rf_failures;
if (GetParam()[i].first == 0.0) {
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(
ipc_thread_->task_runner(), run_loop->QuitClosure(), device_.get(),
std::move(failures), FixFrequency(GetParam()[i].first),
FixFrequency(GetParam()[i].second), max_freq, max_freq2);
handler_->AddClient(&client_data);
loops.emplace_back(std::move(run_loop));
observers_.emplace_back(std::move(fake_observer));
}
pause_loop.Run();
device_->SetPauseCallbackAtKthSamples(
fakes::kPauseIndex,
base::BindOnce(
[](fakes::FakeSamplesHandler* handler,
std::vector<ClientData>* clients_data,
libmems::fakes::FakeIioDevice* device) {
for (int i = 0; i < clients_data->size(); ++i) {
ClientData& client_data = clients_data->at(i);
// Update to the second frequency
handler->UpdateFrequency(
&client_data, GetParam()[i].second,
base::BindOnce(
[](double fixed_new_freq, double result_freq) {
EXPECT_EQ(result_freq, fixed_new_freq);
},
FixFrequency(GetParam()[i].second)));
// Enable accel_y
handler->UpdateChannelsEnabled(
&client_data, std::vector<int32_t>{1}, true,
base::BindOnce(
[](const std::vector<int32_t>& failed_indices) {
EXPECT_TRUE(failed_indices.empty());
}));
}
handler->ResumeReading(); // Read the rest samples
},
handler_.get(), &clients_data_, device_.get()));
handler_->ResumeReading(); // Start reading |kPauseIndex| samples
// Wait until all observers receive all samples
for (auto& run_loop : loops)
run_loop->Run();
// Remove clients
for (auto& client_data : clients_data_)
handler_->RemoveClient(&client_data);
}
INSTANTIATE_TEST_SUITE_P(
SamplesHandlerTestWithParamRun,
SamplesHandlerTestWithParam,
::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
} // namespace iioservice