blob: b82c5ceba96f679e3cf864291ab6cb61270bbc24 [file] [log] [blame]
// Copyright 2020 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.
#pragma once
#include <array>
#include <cstddef>
#include <optional>
#include "pw_bytes/span.h"
#include "pw_containers/to_array.h"
#include "pw_i2c/initiator.h"
namespace pw::i2c {
/// Base class for creating transaction instances. For read-only,
/// write-only, or probe transactions, improve code readability
/// by using one of the following helpers instead:
///
/// * `pw::i2c::ReadTransaction`
/// * `pw::i2c::WriteTransaction`
/// * `pw::i2c::ProbeTransaction`
///
/// If you need to create a write-then-read transaction, you can
/// use this class.
class Transaction {
public:
/// Constructor for creating write-only, read-only, or write-then-read
/// transactions.
constexpr Transaction(
Status expected_return_value,
Address device_address,
ConstByteSpan write_buffer,
ConstByteSpan read_buffer,
std::optional<chrono::SystemClock::duration> timeout = std::nullopt)
: return_value_(expected_return_value),
read_buffer_(read_buffer),
write_buffer_(write_buffer),
address_(device_address),
timeout_(timeout) {}
/// Alternative constructor for creating probe transactions.
constexpr Transaction(
Status expected_return_value,
Address device_address,
std::optional<chrono::SystemClock::duration> timeout = std::nullopt)
: Transaction(expected_return_value,
device_address,
ConstByteSpan(),
ignored_buffer_,
timeout) {}
/// Gets the buffer that is virtually read.
ConstByteSpan read_buffer() const { return read_buffer_; }
/// Gets the buffer that the I2C device should write to.
ConstByteSpan write_buffer() const { return write_buffer_; }
/// Gets the minimum duration to wait for a blocking I2C transaction.
std::optional<chrono::SystemClock::duration> timeout() const {
return timeout_;
}
/// Gets the I2C address that the I2C transaction is targeting.
Address address() const { return address_; }
/// Gets the expected return value for the transaction.
Status return_value() const { return return_value_; }
private:
const Status return_value_;
const ConstByteSpan read_buffer_;
const ConstByteSpan write_buffer_;
static constexpr std::array<std::byte, 1> ignored_buffer_ = {};
const Address address_;
const std::optional<chrono::SystemClock::duration> timeout_;
};
/// A helper that constructs a read-only I2C transaction.
/// Used for testing read transactions with `pw::i2c::MockInitiator`.
constexpr Transaction ReadTransaction(
Status expected_return_value,
Address device_address,
ConstByteSpan read_buffer,
std::optional<chrono::SystemClock::duration> timeout = std::nullopt) {
return Transaction(expected_return_value,
device_address,
ConstByteSpan(),
read_buffer,
timeout);
}
/// A helper that constructs a write-only I2C transaction.
/// Used for testing write transactions with `pw::i2c::MockInitiator`.
constexpr Transaction WriteTransaction(
Status expected_return_value,
Address device_address,
ConstByteSpan write_buffer,
std::optional<chrono::SystemClock::duration> timeout = std::nullopt) {
return Transaction(expected_return_value,
device_address,
write_buffer,
ConstByteSpan(),
timeout);
}
/// A helper that constructs a one-byte read I2C transaction.
/// Used for testing probe transactions with `pw::i2c::MockInitiator`.
constexpr Transaction ProbeTransaction(
Status expected_return_value,
Address device_address,
std::optional<chrono::SystemClock::duration> timeout = std::nullopt) {
return Transaction(expected_return_value, device_address, timeout);
}
/// A generic mocked backend for `pw::i2c::Initiator` that's specifically
/// designed to make it easier to develop I2C device drivers.
/// `pw::i2c::MockInitiator` compares actual I2C transactions against expected
/// transactions. The expected transactions are represented as a list of
/// `pw::i2c::Transaction` instances that are passed as arguments in the
/// `pw::i2c::MockInitiator` constructor. Each consecutive call to
/// `pw::i2c::MockInitiator` iterates to the next expected transaction.
/// `pw::i2c::MockInitiator::Finalize()` indicates whether the actual
/// transactions matched the expected transactions.
///
/// `pw::i2c::MockInitiator` is intended to be used within GoogleTest tests.
/// See @rstref{module-pw_unit_test}.
///
/// @code{.cpp}
/// #include <chrono>
///
/// #include "pw_bytes/array.h"
/// #include "pw_i2c/address.h"
/// #include "pw_i2c/initiator_mock.h"
/// #include "pw_result/result.h"
/// #include "pw_unit_test/framework.h"
///
/// using namespace std::chrono_literals;
///
/// namespace {
///
/// TEST(I2CTestSuite, I2CWriteTestCase) {
/// constexpr pw::i2c::Address kAddress =
/// pw::i2c::Address::SevenBit<0x01>(); constexpr auto kExpectedWrite =
/// pw::bytes::Array<1, 2, 3>(); auto expected_transactions =
/// pw::i2c::MakeExpectedTransactionArray(
/// {pw::i2c::WriteTransaction(pw::OkStatus(), kAddress, kExpectedWrite,
/// 1ms)}
/// );
/// pw::i2c::MockInitiator initiator(expected_transactions);
/// pw::ConstByteSpan kActualWrite = pw::bytes::Array<1, 2, 3>();
/// pw::Status status = initiator.WriteFor(kAddress, kActualWrite, 1ms);
/// EXPECT_EQ(initiator.Finalize(), pw::OkStatus());
/// }
///
/// }
/// @endcode
class MockInitiator : public Initiator {
public:
explicit constexpr MockInitiator(span<Transaction> transaction_list)
: expected_transactions_(transaction_list),
expected_transaction_index_(0) {}
/// Indicates whether the actual I2C transactions matched the expected
/// transactions. Should be called at the end of the test.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: The actual transactions matched the expected transactions.
///
/// OUT_OF_RANGE: The mocked set of transactions hasn't been exhausted.
///
/// @endrst
Status Finalize() const {
if (expected_transaction_index_ != expected_transactions_.size()) {
return Status::OutOfRange();
}
return Status();
}
/// Runs `pw::i2c::MockInitiator::Finalize()` regardless of whether it was
/// already optionally finalized.
~MockInitiator() override;
private:
// Implements a mocked backend for the i2c initiator.
//
// Expects (via Gtest):
// tx_buffer == expected_transaction_tx_buffer
// tx_buffer.size() == expected_transaction_tx_buffer.size()
// rx_buffer.size() == expected_transaction_rx_buffer.size()
//
// Asserts:
// When the number of calls to this method exceed the number of expected
// transactions.
//
// Returns:
// Specified transaction return type
Status DoWriteReadFor(Address device_address,
ConstByteSpan tx_buffer,
ByteSpan rx_buffer,
chrono::SystemClock::duration timeout) override;
span<Transaction> expected_transactions_;
size_t expected_transaction_index_;
};
// Makes a new i2c transactions list.
template <size_t kSize>
constexpr std::array<Transaction, kSize> MakeExpectedTransactionArray(
const Transaction (&transactions)[kSize]) {
return containers::to_array(transactions);
}
} // namespace pw::i2c