| // 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()); |
| }); |
| } |