blob: c3f56be3e6ce9ecd14e0d9d34cc0b6782d83b07a [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_i2c_backend/responder_test.h" // Provided by the backend
#include <array>
#include <vector>
#include "gtest/gtest.h"
#include "pw_bytes/byte_builder.h"
#include "pw_i2c/responder_test_interface.h"
#include "pw_i2c_backend/responder_test.h"
#include "pw_status/status.h"
namespace pw::i2c::test {
namespace {
// The NativeResponderTest class is expected to be defined in
// "pw_i2c_backend/responder_test.h" and inherit from ::testing::Test
// and pw::i2c::test::NativeResponderTestInterface.
//
// class ResponderTest : public NativeResponderTest {};
//
// However, to avoid issues with GTest's TEST_F macro needing the full
// definition of NativeResponderTest if it were a direct base class here,
// we will make ResponderTest a direct child of ::testing::Test and use
// a helper to manage the backend.
// A simpler approach for now, assuming the backend header defines
// a suitable NativeResponderTest that we can inherit from.
// The backend's NativeResponderTest will be like:
// namespace pw::i2c::backend { // Or a more specific namespace
// class NativeResponderTest : public ::testing::Test,
// public
// ::pw::i2c::test::NativeResponderTestInterface {
// public:
// NativeResponderTest(pw::i2c::test::ResponderCallbacksForTest& callbacks);
// // ...
// };
// } // namespace
// If the backend provides NativeResponderTest in a specific namespace,
// adjust the using directive or the class inheritance.
using ::pw::i2c::backend::NativeResponderTest;
class ResponderTest : public NativeResponderTest {
protected:
ResponderTest() : NativeResponderTest() {
// Default implementations for callbacks
backend::kResponderEvents.SetOnStartReadCb([this] {
on_start_read_called_ = true;
return true;
});
backend::kResponderEvents.SetOnStartWriteCb([this] {
on_start_write_called_ = true;
return true;
});
backend::kResponderEvents.SetOnWriteCb([this](ConstByteSpan data) {
on_write_called_ = true;
std::copy(data.begin(), data.end(), std::back_inserter(received_data_));
return true;
});
backend::kResponderEvents.SetOnReadCb([this]() -> Result<ConstByteSpan> {
on_read_called_ = true;
if (read_data_provider_) {
return read_data_provider_();
}
return ByteSpan(read_buffer_); // Return a copy or a managed span
});
backend::kResponderEvents.SetOnStopCb([this] {
on_stop_called_ = true;
return true;
});
GetResponder().Enable().IgnoreError();
}
~ResponderTest() { GetResponder().Disable().IgnoreError(); }
// Helper to reset call flags for each test
void ResetTestState() {
on_start_read_called_ = false;
on_start_write_called_ = false;
on_write_called_ = false;
on_read_called_ = false;
on_stop_called_ = false;
received_data_.clear();
read_buffer_.clear();
read_data_provider_ = nullptr;
}
// Flags to verify callback invocation
bool on_start_read_called_ = false;
bool on_start_write_called_ = false;
bool on_write_called_ = false;
bool on_read_called_ = false;
bool on_stop_called_ = false;
std::vector<std::byte> received_data_;
std::vector<std::byte> read_buffer_; // Data to be returned by OnRead
pw::Function<Result<ByteSpan>()> read_data_provider_;
};
TEST_F(ResponderTest, InitializationIsHandledByBackend) {
// NativeResponderTest::SetUp() is responsible for initializing the
// responder. If it fails, ASSERTs in SetUp() should fail the test.
// GetResponder() should return a valid Responder instance.
ASSERT_NE(&GetResponder(), nullptr);
SUCCEED(); // If we reach here, SetUp didn't crash.
}
TEST_F(ResponderTest, WriteSingleByte) {
ResetTestState();
std::array<std::byte, 1> write_payload = {std::byte{0xAB}};
ASSERT_EQ(OkStatus(),
SimulateInitiatorWrite(ConstByteSpan(write_payload), true));
EXPECT_TRUE(on_start_write_called_);
EXPECT_TRUE(on_write_called_);
EXPECT_EQ(received_data_.size(), 1u);
EXPECT_EQ(received_data_[0], std::byte{0xAB});
EXPECT_TRUE(on_stop_called_);
}
TEST_F(ResponderTest, ReadSingleByte) {
ResetTestState();
read_buffer_ = {std::byte{0xCD}}; // Data our mock OnRead will provide
std::array<std::byte, 1>
initiator_read_buffer; // Buffer for initiator to read into
Status read_result =
SimulateInitiatorRead(ByteSpan(initiator_read_buffer), true);
ASSERT_EQ(OkStatus(), read_result);
EXPECT_TRUE(on_start_read_called_);
EXPECT_TRUE(on_read_called_);
EXPECT_EQ(initiator_read_buffer[0], std::byte{0xCD});
EXPECT_TRUE(on_stop_called_);
}
TEST_F(ResponderTest, WriteMultipleBytes) {
ResetTestState();
std::array<std::byte, 3> write_payload = {
std::byte{0x01}, std::byte{0x02}, std::byte{0x03}};
ASSERT_EQ(OkStatus(),
SimulateInitiatorWrite(ConstByteSpan(write_payload), true));
EXPECT_TRUE(on_start_write_called_);
EXPECT_TRUE(on_write_called_); // May be called multiple times by backend
ASSERT_EQ(received_data_.size(), 3u);
EXPECT_EQ(received_data_[0], std::byte{0x01});
EXPECT_EQ(received_data_[1], std::byte{0x02});
EXPECT_EQ(received_data_[2], std::byte{0x03});
EXPECT_TRUE(on_stop_called_);
}
TEST_F(ResponderTest, ReadMultipleBytes) {
ResetTestState();
read_buffer_ = {std::byte{0x11},
std::byte{0x22},
std::byte{0x33}}; // Data OnRead provides
std::array<std::byte, 3> initiator_read_buffer; // Buffer for initiator
Status read_result =
SimulateInitiatorRead(ByteSpan(initiator_read_buffer), true);
ASSERT_EQ(OkStatus(), read_result);
EXPECT_TRUE(on_start_read_called_);
EXPECT_TRUE(on_read_called_); // May be called multiple times by backend
EXPECT_EQ(initiator_read_buffer[0], std::byte{0x11});
EXPECT_EQ(initiator_read_buffer[1], std::byte{0x22});
EXPECT_EQ(initiator_read_buffer[2], std::byte{0x33});
EXPECT_TRUE(on_stop_called_);
}
TEST_F(ResponderTest, OnStartWriteReturnsError) {
ResetTestState();
std::array<std::byte, 1> write_payload = {std::byte{0xFF}};
backend::kResponderEvents.SetOnStartWriteCb([this] {
on_start_write_called_ = true;
return false; // Simulate error
});
// When writing using pio (programmed input/output) the start event is
// ACKed based on the result of the OnStart() function. But when the
// controller is running in buffered mode, the start condition is
// automatically ACKed by the hardware and the data will go through regardless
// of the OnStart() result. Therefore we cannot make any assumptions about the
// simulated write, it will fail when the bus is running in pio mode but will
// pass in buffered mode.
SimulateInitiatorWrite(ConstByteSpan(write_payload), true).IgnoreError();
EXPECT_TRUE(on_start_write_called_);
EXPECT_FALSE(on_write_called_); // Should not be called if start fails
// Similar to the above, we will get a stop condition in buffered mode, but
// will never get to the stop condition when running in pio mode. This means
// we cannot expect a stop in a generic test.
}
TEST_F(ResponderTest, OnStartReadReturnsError) {
ResetTestState();
backend::kResponderEvents.SetOnStartReadCb([this] {
on_start_read_called_ = true;
return false; // Simulate error
});
std::array<std::byte, 1> initiator_read_buffer;
Status read_result =
SimulateInitiatorRead(ByteSpan(initiator_read_buffer), true);
// Expect the simulation to report an error.
// The exact error might be backend-dependent.
EXPECT_NE(OkStatus(), read_result);
// Depending on the backend, value() might not be valid if status is not OK.
// If it returns a size, it should likely be 0 on error.
// if (read_result.ok()) { EXPECT_EQ(read_result.value(), 0u); }
EXPECT_TRUE(on_start_read_called_);
EXPECT_FALSE(on_read_called_);
// Whether OnStop is called can be backend-dependent if the transaction aborts
// early. The test plan suggests OnStop is not called. Let's assume that for
// now. If the backend *does* call OnStop, this expectation might need
// adjustment or the backend simulation needs to be more specific.
EXPECT_FALSE(on_stop_called_);
}
} // namespace
} // namespace pw::i2c::test