blob: 7161c53ef1a79ef682d1261bf554416e9e2464e7 [file] [log] [blame]
// Copyright 2023 The Fuchsia 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 "pcf8563.h"
#include <fidl/fuchsia.hardware.i2c/cpp/fidl.h>
#include <fidl/fuchsia.hardware.rtc/cpp/fidl.h>
#include <fidl/fuchsia.io/cpp/fidl.h>
#include <lib/async_patterns/testing/cpp/dispatcher_bound.h>
#include <lib/driver/testing/cpp/driver_lifecycle.h>
#include <lib/driver/testing/cpp/driver_runtime.h>
#include <lib/driver/testing/cpp/test_environment.h>
#include <lib/driver/testing/cpp/test_node.h>
#include <lib/fdf/cpp/dispatcher.h>
#include <lib/fdio/directory.h>
#include <lib/fit/result.h>
#include <lib/zx/result.h>
#include <zircon/errors.h>
#include <functional>
#include <optional>
#include <string>
#include <utility>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace fi2c = fuchsia_hardware_i2c;
namespace frtc = fuchsia_hardware_rtc;
class MockI2c : public fidl::Server<fi2c::Device> {
public:
fidl::ProtocolHandler<fi2c::Device> GetHandler() {
auto* dispatcher = fdf::Dispatcher::GetCurrent()->async_dispatcher();
return bindings_.CreateHandler(this, dispatcher, fidl::kIgnoreBindingClosure);
}
// I2c FIDL protocol methods.
MOCK_METHOD(void, Transfer, (TransferRequest&, TransferCompleter::Sync&), (override));
MOCK_METHOD(void, GetName, (GetNameCompleter::Sync&), (override));
private:
fidl::ServerBindingGroup<fi2c::Device> bindings_;
};
// This class is templated with a bool parameter to indicate whether it should perform DFv2 driver
// lifetime management. If false, it will be the individual test(s) responsibility to start and stop
// the driver instance manually.
template <bool manage_lifetime>
class BaseTest : public testing::Test {
void SetUp() override {
namespace_.SyncCall([&](Wrapper* ctx) mutable {
fi2c::Service::InstanceHandler handler({.device = ctx->i2c.GetHandler()});
auto& incoming = ctx->env.incoming_directory();
auto result = incoming.template AddService<fi2c::Service>(std::move(handler));
ASSERT_EQ(ZX_OK, result.status_value());
});
if (manage_lifetime) {
auto start_args =
namespace_.SyncCall([&](Wrapper* ctx) { return ctx->node.CreateStartArgsAndServe(); });
ASSERT_EQ(ZX_OK, start_args.status_value());
zx::result init_result = namespace_.SyncCall([&](Wrapper* ctx) {
return ctx->env.Initialize(std::move(start_args->incoming_directory_server));
});
ASSERT_EQ(ZX_OK, init_result.status_value());
// Driver Start()
zx::result start_result =
runtime_.RunToCompletion(driver_.Start(std::move(start_args->start_args)));
ASSERT_EQ(ZX_OK, start_result.status_value());
auto dir = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_EQ(ZX_OK, dir.status_value());
// Connect via the driver's outgoing namespace.
zx_status_t status =
fdio_open_at(start_args->outgoing_directory_client.handle()->get(), "/svc",
static_cast<uint32_t>(fuchsia_io::OpenFlags::kDirectory),
dir->server.TakeChannel().release());
ASSERT_EQ(ZX_OK, status);
client_.Bind(
component::ConnectAtMember<frtc::Service::Device>(std::move(dir->client)).value());
ASSERT_TRUE(client_.is_valid());
}
namespace_.SyncCall([&](Wrapper* ctx) { mock_ = &ctx->i2c; });
}
void TearDown() override {
if (manage_lifetime) {
zx::result prepare_stop_result = runtime_.RunToCompletion(driver_.PrepareStop());
ASSERT_EQ(ZX_OK, prepare_stop_result.status_value());
}
}
protected:
static void RunSyncClientTask(fit::closure task) {
async::Loop loop{&kAsyncLoopConfigNeverAttachToThread};
loop.StartThread();
zx::result result = fdf::RunOnDispatcherSync(loop.dispatcher(), std::move(task));
ASSERT_EQ(ZX_OK, result.status_value());
}
struct Wrapper {
Wrapper() : node{"root"} {}
MockI2c i2c;
// N.B. TestNode serves both Node and NodeController protocols.
fdf_testing::TestNode node;
fdf_testing::TestEnvironment env;
};
fdf_testing::DriverRuntime runtime_;
fdf::UnownedSynchronizedDispatcher dispatcher_{runtime_.StartBackgroundDispatcher()};
async_patterns::TestDispatcherBound<Wrapper> namespace_{dispatcher_->async_dispatcher(),
std::in_place};
fdf_testing::DriverUnderTest<pcf8563::RtcDriver> driver_;
// Only use this for mock callable configuration/assertion.
MockI2c* mock_;
fidl::SyncClient<frtc::Device> client_;
};
using UnmanagedRtcDriverTest = BaseTest<false>;
using ManagedRtcDriverTest = BaseTest<true>;
TEST_F(UnmanagedRtcDriverTest, TestDfv2StartStop) {
auto start_args =
namespace_.SyncCall([&](Wrapper* ctx) { return ctx->node.CreateStartArgsAndServe(); });
EXPECT_EQ(ZX_OK, start_args.status_value());
zx::result init_result = namespace_.SyncCall([&](Wrapper* ctx) {
return ctx->env.Initialize(std::move(start_args->incoming_directory_server));
});
EXPECT_EQ(ZX_OK, init_result.status_value());
// Driver Start()
zx::result start_result =
runtime_.RunToCompletion(driver_.Start(std::move(start_args->start_args)));
EXPECT_EQ(ZX_OK, start_result.status_value());
// Driver PrepareStop()
zx::result prepare_stop_result = runtime_.RunToCompletion(driver_.PrepareStop());
EXPECT_EQ(ZX_OK, prepare_stop_result.status_value());
// Driver Stop()
zx::result stop_result = driver_.Stop();
EXPECT_EQ(ZX_OK, stop_result.status_value());
}
TEST_F(ManagedRtcDriverTest, TestReadFailsOnUpsteramError) {
auto action = [&](MockI2c::TransferRequest& req, MockI2c::TransferCompleter::Sync& comp) {
comp.Reply(zx::error(ZX_ERR_INTERNAL));
};
EXPECT_CALL(*mock_, Transfer).WillOnce(action);
RunSyncClientTask([&]() {
auto result = client_->Get();
ASSERT_TRUE(result.is_error());
ASSERT_TRUE(result.error_value().is_domain_error());
ASSERT_EQ(ZX_ERR_INTERNAL, result.error_value().domain_error());
});
}
TEST_F(ManagedRtcDriverTest, TestWriteFailsOnUpsteramError) {
auto action = [&](MockI2c::TransferRequest& req, MockI2c::TransferCompleter::Sync& comp) {
comp.Reply(zx::error(ZX_ERR_INTERNAL));
};
EXPECT_CALL(*mock_, Transfer).WillOnce(action);
frtc::Time time;
time.year(1900);
RunSyncClientTask([&]() {
auto result = client_->Set(time);
// Set() currently isn't using -> error syntax, and returns errors in the struct.
ASSERT_FALSE(result.is_error());
ASSERT_EQ(ZX_ERR_INTERNAL, result.value().status());
});
}
TEST_F(ManagedRtcDriverTest, TestReadSuccess) {
auto action = [&](MockI2c::TransferRequest& req, MockI2c::TransferCompleter::Sync& comp) {
fi2c::DeviceTransferResponse resp{{
.read_data = {std::vector<uint8_t>{
0x58, // Seconds.
0x40, // Minutes.
0x16, // Hours.
0x20, // Day.
0x00, // Day-of-week (unused).
0x91, // Month with the high-bit indicating century: 0x80 ? 2000 : 1900.
0x22, // Year relative to century.
}},
}};
comp.Reply(zx::ok(std::move(resp)));
};
EXPECT_CALL(*mock_, Transfer).WillOnce(action);
RunSyncClientTask([&]() {
auto result = client_->Get();
ASSERT_TRUE(result.is_ok());
EXPECT_EQ(58, result.value().rtc().seconds());
EXPECT_EQ(40, result.value().rtc().minutes());
EXPECT_EQ(16, result.value().rtc().hours());
EXPECT_EQ(20, result.value().rtc().day());
EXPECT_EQ(11, result.value().rtc().month());
EXPECT_EQ(2022, result.value().rtc().year());
});
}
TEST_F(ManagedRtcDriverTest, TestReadCentury1900) {
auto action = [&](MockI2c::TransferRequest& req, MockI2c::TransferCompleter::Sync& comp) {
fi2c::DeviceTransferResponse resp{{
.read_data = {std::vector<uint8_t>{
0x00, // Seconds.
0x00, // Minutes.
0x00, // Hours.
0x00, // Day.
0x00, // Day-of-week (unused).
0x11, // Month with the high-bit indicating century: 0x80 ? 2000 : 1900.
0x22, // Year relative to century.
}},
}};
comp.Reply(zx::ok(std::move(resp)));
};
EXPECT_CALL(*mock_, Transfer).WillOnce(action);
RunSyncClientTask([&]() {
auto result = client_->Get();
ASSERT_TRUE(result.is_ok());
EXPECT_EQ(11, result.value().rtc().month());
EXPECT_EQ(1922, result.value().rtc().year());
});
}
TEST_F(ManagedRtcDriverTest, TestReadCentury2000) {
auto action = [&](MockI2c::TransferRequest& req, MockI2c::TransferCompleter::Sync& comp) {
fi2c::DeviceTransferResponse resp{{
.read_data = {std::vector<uint8_t>{
0x00, // Seconds.
0x00, // Minutes.
0x00, // Hours.
0x00, // Day.
0x00, // Day-of-week (unused).
0x91, // Month with the high-bit indicating century: 0x80 ? 2000 : 1900.
0x22, // Year relative to century.
}},
}};
comp.Reply(zx::ok(std::move(resp)));
};
EXPECT_CALL(*mock_, Transfer).WillOnce(action);
RunSyncClientTask([&]() {
auto result = client_->Get();
ASSERT_TRUE(result.is_ok());
EXPECT_EQ(11, result.value().rtc().month());
EXPECT_EQ(2022, result.value().rtc().year());
});
}
TEST_F(ManagedRtcDriverTest, TestWriteSuccess) {
auto action = [&](MockI2c::TransferRequest& req, MockI2c::TransferCompleter::Sync& comp) {
ASSERT_EQ(1, req.transactions().size());
auto& txn = req.transactions()[0];
auto data = static_cast<std::vector<uint8_t>>(txn.data_transfer()->write_data().value());
ASSERT_EQ(8, data.size());
EXPECT_EQ(0x02, data[0]); // I2c destination register.
EXPECT_EQ(0x58, data[1]); // Seconds.
EXPECT_EQ(0x40, data[2]); // Minutes.
EXPECT_EQ(0x16, data[3]); // Hour.
EXPECT_EQ(0x20, data[4]); // Day.
// day-of-week is unused.
EXPECT_EQ(0x91, data[6]); // Month with high-bit indicating century: 0x80 ? 2000 : 1900.
EXPECT_EQ(0x22, data[7]); // Year relative to century.
// Write transactions result in no read_data.
fi2c::DeviceTransferResponse resp;
comp.Reply(zx::ok(std::move(resp)));
};
EXPECT_CALL(*mock_, Transfer).WillOnce(action);
frtc::Time time;
time.seconds(58);
time.minutes(40);
time.hours(16);
time.day(20);
time.month(11);
time.year(2022);
RunSyncClientTask([&]() {
auto result = client_->Set(time);
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(ZX_OK, result.value().status());
});
}
TEST_F(ManagedRtcDriverTest, TestWriteCentury1900) {
auto action = [&](MockI2c::TransferRequest& req, MockI2c::TransferCompleter::Sync& comp) {
auto& txn = req.transactions()[0];
auto data = static_cast<std::vector<uint8_t>>(txn.data_transfer()->write_data().value());
EXPECT_EQ(0x00, data[6]); // Month with high-bit indicating century: 0x80 ? 2000 : 1900.
EXPECT_EQ(0x22, data[7]); // Year relative to century.
fi2c::DeviceTransferResponse resp;
comp.Reply(zx::ok(std::move(resp)));
};
EXPECT_CALL(*mock_, Transfer).WillOnce(action);
frtc::Time time;
time.year(1922);
RunSyncClientTask([&]() {
auto result = client_->Set(time);
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(ZX_OK, result.value().status());
});
}
TEST_F(ManagedRtcDriverTest, TestWriteCentury2000) {
auto action = [&](MockI2c::TransferRequest& req, MockI2c::TransferCompleter::Sync& comp) {
auto& txn = req.transactions()[0];
auto data = static_cast<std::vector<uint8_t>>(txn.data_transfer()->write_data().value());
EXPECT_EQ(0x80, data[6]); // Month with high-bit indicating century: 0x80 ? 2000 : 1900.
EXPECT_EQ(0x22, data[7]); // Year relative to century.
fi2c::DeviceTransferResponse resp;
comp.Reply(zx::ok(std::move(resp)));
};
EXPECT_CALL(*mock_, Transfer).WillOnce(action);
frtc::Time time;
time.year(2022);
RunSyncClientTask([&]() {
auto result = client_->Set(time);
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(ZX_OK, result.value().status());
});
}
TEST_F(ManagedRtcDriverTest, TestWriteCenturyBoundsChecking) {
auto action = [&](MockI2c::TransferRequest& req, MockI2c::TransferCompleter::Sync& comp) {
fi2c::DeviceTransferResponse resp;
comp.Reply(zx::ok(std::move(resp)));
};
// Twice, once for the 1900 branch and then again for the 2099 branch.
EXPECT_CALL(*mock_, Transfer).WillOnce(action).WillOnce(action);
frtc::Time time;
time.year(1899);
RunSyncClientTask([&]() {
auto result = client_->Set(time);
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(ZX_ERR_OUT_OF_RANGE, result.value().status());
});
time.year(1900);
RunSyncClientTask([&]() {
auto result = client_->Set(time);
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(ZX_OK, result.value().status());
});
time.year(2099);
RunSyncClientTask([&]() {
auto result = client_->Set(time);
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(ZX_OK, result.value().status());
});
time.year(2100);
RunSyncClientTask([&]() {
auto result = client_->Set(time);
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(ZX_ERR_OUT_OF_RANGE, result.value().status());
});
}