pw_i2c: Adds a mocked initiator implementation

This is useful as a reusable mock when writing drivers. Specifically
this allows a driver developer to specify a set of mocked i2c
transactions. This does not require physical hardware and is ideal for
unit testing a driver.

Change-Id: I4e0f13450f84feeefeef534b6c46192462c18a8b
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/37920
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Commit-Queue: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com>
diff --git a/pw_i2c/BUILD b/pw_i2c/BUILD
index 7ce2297..820c9fe 100644
--- a/pw_i2c/BUILD
+++ b/pw_i2c/BUILD
@@ -89,6 +89,33 @@
     ],
     deps = [
         ":address",
+        "//pw_assert",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_library(
+    name = "initiator_mock",
+    testonly = True,
+    srcs = ["initiator_mock.cc"],
+    hdrs = ["public/pw_i2c/initiator_mock.h"],
+    includes = ["public"],
+    deps = [
+        ":address",
+        ":initiator",
+        "//pw_assert",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "initiator_mock_test",
+    srcs = [
+        "initiator_mock_test.cc",
+    ],
+    deps = [
+        ":initiator_mock",
+        "//pw_bytes",
         "//pw_unit_test",
     ],
 )
diff --git a/pw_i2c/BUILD.gn b/pw_i2c/BUILD.gn
index 81fa6c8..99ba0c3 100644
--- a/pw_i2c/BUILD.gn
+++ b/pw_i2c/BUILD.gn
@@ -69,6 +69,17 @@
   deps = [ "$dir_pw_assert" ]
 }
 
+pw_source_set("mock") {
+  public_configs = [ ":public_include_path" ]
+  public = [ "public/pw_i2c/initiator_mock.h" ]
+  sources = [ "initiator_mock.cc" ]
+  public_deps = [
+    ":initiator",
+    "$dir_pw_assert",
+  ]
+}
+
+# TODO: add mock_test here once chrono backend is supported for stm32f429i-disc1
 pw_test_group("tests") {
   tests = [
     ":address_test",
@@ -97,6 +108,11 @@
   ]
 }
 
+pw_test("initiator_mock_test") {
+  sources = [ "initiator_mock_test.cc" ]
+  deps = [ ":initiator_mock" ]
+}
+
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_i2c/docs.rst b/pw_i2c/docs.rst
index 976820e..9129edc 100644
--- a/pw_i2c/docs.rst
+++ b/pw_i2c/docs.rst
@@ -33,3 +33,45 @@
 understanding of the capabilities of their device such as register address
 sizes, register data sizes, byte addressability, bulk transactions, etc in
 order to effectively use this interface.
+
+pw::i2c::MockInitiator
+----------------------
+
+A generic mocked backend for for pw::i2c::Initiator. This is specifically
+intended for use when developing drivers for i2c devices. This is structured
+around a set of 'transactions' where each transaction contains a write, read and
+a timeout. A transaction list can then be passed to the MockInitiator, where
+each consecutive call to read/write will iterate to the next transaction in the
+list. An example of this is shown below:
+
+.. code-block:: cpp
+
+  using pw::i2c::Address;
+  using pw::i2c::MakeExpectedTransactionlist;
+  using pw::i2c::MockInitiator;
+  using pw::i2c::WriteTransaction;
+  using std::literals::chrono_literals::ms;
+
+  constexpr Address kAddress1 = Address::SevenBit<0x01>();
+  constexpr auto kExpectWrite1 = pw::bytes::Array<1, 2, 3, 4, 5>();
+  constexpr auto kExpectWrite2 = pw::bytes::Array<3, 4, 5>();
+  auto expected_transactions = MakeExpectedTransactionArray(
+      {WriteTransaction(pw::OkStatus(), kAddress1, kExpectWrite1, 1ms),
+       WriteTransaction(pw::OkStatus(), kAddress2, kExpectWrite2, 1ms)});
+  MockInitiator i2c_mock(expected_transactions);
+
+  // Begin driver code
+  ConstByteSpan write1 = kExpectWrite1;
+  // write1 is ok as i2c_mock expects {1, 2, 3, 4, 5} == {1, 2, 3, 4, 5}
+  Status status = i2c_mock.WriteFor(kAddress1, write1, 2ms);
+
+  // Takes the first two bytes from the expected array to build a mismatching
+  // span to write.
+  ConstByteSpan write2 = std::span(kExpectWrite2).first(2);
+  // write2 fails as i2c_mock expects {3, 4, 5} != {3, 4}
+  status = i2c_mock.WriteFor(kAddress2, write2, 2ms);
+  // End driver code
+
+  // Optionally check if the mocked transaction list has been exhausted.
+  // Alternatively this is also called from MockInitiator::~MockInitiator().
+  EXPECT_EQ(mocked_i2c.Finalize(), OkStatus());
\ No newline at end of file
diff --git a/pw_i2c/initiator_mock.cc b/pw_i2c/initiator_mock.cc
new file mode 100644
index 0000000..b0d2997
--- /dev/null
+++ b/pw_i2c/initiator_mock.cc
@@ -0,0 +1,65 @@
+// 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.
+#include "pw_i2c/initiator_mock.h"
+
+#include <iostream>
+
+#include "pw_assert/check.h"
+
+namespace pw::i2c {
+
+Status MockInitiator::DoWriteReadFor(
+    Address device_address,
+    ConstByteSpan tx_buffer,
+    ByteSpan rx_buffer,
+    chrono::SystemClock::duration for_at_least) {
+  PW_CHECK_INT_LT(expected_transaction_index_, expected_transactions_.size());
+
+  EXPECT_EQ(
+      expected_transactions_[expected_transaction_index_].address().GetTenBit(),
+      device_address.GetTenBit());
+
+  auto expected_for_at_least =
+      expected_transactions_[expected_transaction_index_].for_at_least();
+  if (expected_for_at_least.has_value()) {
+    EXPECT_EQ(expected_for_at_least.value(), for_at_least);
+  }
+
+  ConstByteSpan expected_tx_buffer =
+      expected_transactions_[expected_transaction_index_].write_buffer();
+  EXPECT_TRUE(std::equal(expected_tx_buffer.begin(),
+                         expected_tx_buffer.end(),
+                         tx_buffer.begin(),
+                         tx_buffer.end()));
+
+  ConstByteSpan expected_rx_buffer =
+      expected_transactions_[expected_transaction_index_].read_buffer();
+  EXPECT_EQ(expected_rx_buffer.size(), rx_buffer.size());
+
+  std::copy(
+      expected_rx_buffer.begin(), expected_rx_buffer.end(), rx_buffer.begin());
+
+  // Do not directly return this value as expected_transaction_index_ should be
+  // incremented.
+  const Status expected_return_value =
+      expected_transactions_[expected_transaction_index_].return_value();
+
+  expected_transaction_index_ += 1;
+
+  return expected_return_value;
+}
+
+MockInitiator::~MockInitiator() { EXPECT_EQ(Finalize(), OkStatus()); }
+
+}  // namespace pw::i2c
\ No newline at end of file
diff --git a/pw_i2c/initiator_mock_test.cc b/pw_i2c/initiator_mock_test.cc
new file mode 100644
index 0000000..beed22f
--- /dev/null
+++ b/pw_i2c/initiator_mock_test.cc
@@ -0,0 +1,108 @@
+// Copyright 2021 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/initiator_mock.h"
+
+#include <array>
+#include <chrono>
+#include <span>
+
+#include "gtest/gtest.h"
+#include "pw_bytes/array.h"
+#include "pw_bytes/span.h"
+#include "pw_i2c/address.h"
+
+using namespace std::literals::chrono_literals;
+
+namespace pw::i2c {
+namespace {
+
+TEST(Transaction, Read) {
+  static constexpr Address kAddress1 = Address::SevenBit<0x01>();
+  constexpr auto kExpectRead1 = bytes::Array<1, 2, 3, 4, 5>();
+
+  static constexpr Address kAddress2 = Address::SevenBit<0x02>();
+  constexpr auto kExpectRead2 = bytes::Array<3, 4, 5>();
+
+  auto expected_transactions = MakeExpectedTransactionArray(
+      {ReadTransaction(OkStatus(), kAddress1, kExpectRead1, 2ms),
+       ReadTransaction(OkStatus(), kAddress2, kExpectRead2, 2ms)});
+
+  MockInitiator mocked_i2c(expected_transactions);
+
+  std::array<std::byte, kExpectRead1.size()> read1;
+  EXPECT_EQ(mocked_i2c.ReadFor(kAddress1, read1, 2ms), OkStatus());
+  EXPECT_TRUE(std::equal(
+      read1.begin(), read1.end(), kExpectRead1.begin(), kExpectRead1.end()));
+
+  std::array<std::byte, kExpectRead2.size()> read2;
+  EXPECT_EQ(mocked_i2c.ReadFor(kAddress2, read2, 2ms), OkStatus());
+  EXPECT_TRUE(std::equal(
+      read2.begin(), read2.end(), kExpectRead2.begin(), kExpectRead2.end()));
+
+  EXPECT_EQ(mocked_i2c.Finalize(), OkStatus());
+}
+
+TEST(Transaction, Write) {
+  static constexpr Address kAddress1 = Address::SevenBit<0x01>();
+  constexpr auto kExpectWrite1 = bytes::Array<1, 2, 3, 4, 5>();
+
+  static constexpr Address kAddress2 = Address::SevenBit<0x02>();
+  constexpr auto kExpectWrite2 = bytes::Array<3, 4, 5>();
+
+  auto expected_transactions = MakeExpectedTransactionArray(
+      {WriteTransaction(OkStatus(), kAddress1, kExpectWrite1, 2ms),
+       WriteTransaction(OkStatus(), kAddress2, kExpectWrite2, 2ms)});
+
+  MockInitiator mocked_i2c(expected_transactions);
+
+  EXPECT_EQ(mocked_i2c.WriteFor(kAddress1, kExpectWrite1, 2ms), OkStatus());
+
+  EXPECT_EQ(mocked_i2c.WriteFor(kAddress2, kExpectWrite2, 2ms), OkStatus());
+
+  EXPECT_EQ(mocked_i2c.Finalize(), OkStatus());
+}
+
+TEST(Transaction, WriteRead) {
+  static constexpr Address kAddress1 = Address::SevenBit<0x01>();
+  constexpr auto kExpectWrite1 = bytes::Array<1, 2, 3, 4, 5>();
+  constexpr auto kExpectRead1 = bytes::Array<1, 2>();
+
+  static constexpr Address kAddress2 = Address::SevenBit<0x02>();
+  constexpr auto kExpectWrite2 = bytes::Array<3, 4, 5>();
+  constexpr const auto kExpectRead2 = bytes::Array<3, 4>();
+
+  auto expected_transactions = MakeExpectedTransactionArray({
+      Transaction(OkStatus(), kAddress1, kExpectWrite1, kExpectRead1, 2ms),
+      Transaction(OkStatus(), kAddress2, kExpectWrite2, kExpectRead2, 2ms),
+  });
+
+  MockInitiator mocked_i2c(expected_transactions);
+
+  std::array<std::byte, kExpectRead1.size()> read1;
+  EXPECT_EQ(mocked_i2c.WriteReadFor(kAddress1, kExpectWrite1, read1, 2ms),
+            OkStatus());
+  EXPECT_TRUE(std::equal(read1.begin(), read1.end(), kExpectRead1.begin()));
+
+  std::array<std::byte, kExpectRead1.size()> read2;
+  EXPECT_EQ(mocked_i2c.WriteReadFor(kAddress2, kExpectWrite2, read2, 2ms),
+            OkStatus());
+  EXPECT_TRUE(std::equal(
+      read2.begin(), read2.end(), kExpectRead2.begin(), kExpectRead2.end()));
+
+  EXPECT_EQ(mocked_i2c.Finalize(), OkStatus());
+}
+
+}  // namespace
+}  // namespace pw::i2c
\ No newline at end of file
diff --git a/pw_i2c/public/pw_i2c/initiator_mock.h b/pw_i2c/public/pw_i2c/initiator_mock.h
new file mode 100644
index 0000000..d7dd094
--- /dev/null
+++ b/pw_i2c/public/pw_i2c/initiator_mock.h
@@ -0,0 +1,150 @@
+// 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 "gtest/gtest.h"
+#include "initiator.h"
+#include "pw_bytes/span.h"
+
+namespace pw::i2c {
+
+// Represents a complete parameter set for the Initiator::DoWriteReadFor().
+class Transaction {
+ public:
+  // Same set of parameters as  Initiator::DoWriteReadFor(), with the exception
+  // of optional parameter for_at_least.
+  constexpr Transaction(
+      Status expected_return_value,
+      Address device_address,
+      ConstByteSpan write_buffer,
+      ConstByteSpan read_buffer,
+      std::optional<chrono::SystemClock::duration> for_at_least = std::nullopt)
+      : return_value_(expected_return_value),
+        read_buffer_(read_buffer),
+        write_buffer_(write_buffer),
+        address_(device_address),
+        for_at_least_(for_at_least) {}
+
+  // Gets the buffer that is virtually read.
+  ConstByteSpan read_buffer() const { return read_buffer_; }
+
+  // Gets the buffer that should be written by the driver.
+  ConstByteSpan write_buffer() const { return write_buffer_; }
+
+  // Gets the min duration for a blocking i2c transaction.
+  std::optional<chrono::SystemClock::duration> for_at_least() const {
+    return for_at_least_;
+  }
+
+  // Gets the i2c address that the i2c transaction is targetting.
+  Address address() const { return address_; }
+
+  // Gets the expected return value.
+  Status return_value() const { return return_value_; }
+
+ private:
+  const Status return_value_;
+  const ConstByteSpan read_buffer_;
+  const ConstByteSpan write_buffer_;
+  const Address address_;
+  const std::optional<chrono::SystemClock::duration> for_at_least_;
+};
+
+// Read transaction is a helper that constructs a read only transaction.
+constexpr Transaction ReadTransaction(
+    Status expected_return_value,
+    Address device_address,
+    ConstByteSpan read_buffer,
+    std::optional<chrono::SystemClock::duration> for_at_least = std::nullopt) {
+  return std::move(Transaction(expected_return_value,
+                               device_address,
+                               ConstByteSpan(),
+                               read_buffer,
+                               for_at_least));
+}
+
+// WriteTransaction is a helper that constructs a write only transaction.
+constexpr Transaction WriteTransaction(
+    Status expected_return_value,
+    Address device_address,
+    ConstByteSpan write_buffer,
+    std::optional<chrono::SystemClock::duration> for_at_least = std::nullopt) {
+  return std::move(Transaction(expected_return_value,
+                               device_address,
+                               write_buffer,
+                               ConstByteSpan(),
+                               for_at_least));
+}
+
+// MockInitiator takes a series of read and/or write transactions and
+// compares them against user/driver input.
+//
+// This mock uses Gtest to ensure that the transactions instantiated meet
+// expectations. This MockedInitiator should be instantiated inside a Gtest test
+// frame.
+class MockInitiator : public Initiator {
+ public:
+  explicit constexpr MockInitiator(std::span<Transaction> transaction_list)
+      : expected_transactions_(transaction_list),
+        expected_transaction_index_(0) {}
+
+  // Should be called at the end of the test to ensure that all expected
+  // transactions have been met.
+  // Returns:
+  // Ok - Success.
+  // OutOfRange - The mocked set of transactions has not been exhausted.
+  Status Finalize() const {
+    if (expected_transaction_index_ != expected_transactions_.size()) {
+      return Status::OutOfRange();
+    }
+    return Status();
+  }
+
+  // Runs Finalize() regardless of whether it was already optionally finalized.
+  ~MockInitiator();
+
+ 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 for_at_least) override;
+
+  std::span<Transaction> expected_transactions_;
+  size_t expected_transaction_index_;
+};
+
+// Makes a new i2c transactions list.
+template <size_t size>
+constexpr std::array<Transaction, size> MakeExpectedTransactionArray(
+    const Transaction (&transactions)[size]) {
+  return std::to_array(transactions);
+}
+
+}  // namespace pw::i2c
\ No newline at end of file