Skip to content

Commit

Permalink
pw_spi: Add mock SPI initiator
Browse files Browse the repository at this point in the history
Add a mock initiator implementation that allows tests to specify a list
of expected transactions.

Change-Id: Ifb5c1bedd9468202f11a380766e1ec4091055dae
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/92147
Reviewed-by: Mark Slevinsky <markslevinsky@google.com>
Commit-Queue: Austin Foxley <afoxley@google.com>
  • Loading branch information
afoxley authored and CQ Bot Account committed Apr 22, 2022
1 parent 4ef3c2a commit 7cabfff
Show file tree
Hide file tree
Showing 7 changed files with 401 additions and 1 deletion.
30 changes: 30 additions & 0 deletions pw_spi/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,36 @@ pw_cc_library(
],
)

pw_cc_library(
name = "initiator_mock",
testonly = True,
srcs = ["initiator_mock.cc"],
hdrs = [
"public/pw_spi/chip_selector_mock.h",
"public/pw_spi/initiator_mock.h",
],
includes = ["public"],
deps = [
":chip_selector",
":initiator",
"//pw_assert",
"//pw_containers:to_array",
"//pw_unit_test",
],
)

pw_cc_test(
name = "initiator_mock_test",
srcs = [
"initiator_mock_test.cc",
],
deps = [
":initiator_mock",
"//pw_bytes",
"//pw_unit_test",
],
)

pw_cc_library(
name = "device",
hdrs = [
Expand Down
29 changes: 28 additions & 1 deletion pw_spi/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ pw_source_set("device") {
]
}

pw_source_set("mock") {
public_configs = [ ":public_include_path" ]
public = [
"public/pw_spi/chip_selector_mock.h",
"public/pw_spi/initiator_mock.h",
]
sources = [ "initiator_mock.cc" ]
public_deps = [
":chip_selector",
":initiator",
"$dir_pw_bytes",
"$dir_pw_containers:to_array",
]
deps = [
"$dir_pw_assert",
"$dir_pw_unit_test",
]
}

# Linux-specific spidev implementation.
pw_source_set("linux_spi") {
public_configs = [ ":public_include_path" ]
Expand All @@ -77,7 +96,10 @@ pw_source_set("linux_spi") {
}

pw_test_group("tests") {
tests = [ ":spi_test" ]
tests = [
":spi_test",
":initiator_mock_test",
]
}

pw_test("spi_test") {
Expand All @@ -88,6 +110,11 @@ pw_test("spi_test") {
]
}

pw_test("initiator_mock_test") {
sources = [ "initiator_mock_test.cc" ]
deps = [ ":mock" ]
}

# Linux tests currently only work on a target with spidev support and a SPI endpoint
# mounted at /dev/spidev0.0
pw_test("linux_spi_test") {
Expand Down
38 changes: 38 additions & 0 deletions pw_spi/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -336,3 +336,41 @@ the ``pw::sync::Borrowable`` object, where the ``pw::spi::Initiator`` object is
Returns OkStatus() on success, and implementation-specific values on
failure.

pw::spi::MockInitiator
----------------------
A generic mocked backend for for pw::spi::Initiator. This is specifically
intended for use when developing drivers for spi devices. This is structured
around a set of 'transactions' where each transaction contains a write, read and
a status. 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::spi::MakeExpectedTransactionlist;
using pw::spi::MockInitiator;
using pw::spi::MockWriteTransaction;
constexpr auto kExpectWrite1 = pw::bytes::Array<1, 2, 3, 4, 5>();
constexpr auto kExpectWrite2 = pw::bytes::Array<3, 4, 5>();
auto expected_transactions = MakeExpectedTransactionArray(
{MockWriteTransaction(pw::OkStatus(), kExpectWrite1),
MockWriteTransaction(pw::OkStatus(), kExpectWrite2)});
MockInitiator spi_mock(expected_transactions);
// Begin driver code
ConstByteSpan write1 = kExpectWrite1;
// write1 is ok as spi_mock expects {1, 2, 3, 4, 5} == {1, 2, 3, 4, 5}
Status status = spi_mock.WriteRead(write1, ConstByteSpan());
// 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 spi_mock expects {3, 4, 5} != {3, 4}
status = spi_mock.WriteRead(write2, ConstByteSpan());
// End driver code
// Optionally check if the mocked transaction list has been exhausted.
// Alternatively this is also called from MockInitiator::~MockInitiator().
EXPECT_EQ(spi_mock.Finalize(), OkStatus());
51 changes: 51 additions & 0 deletions pw_spi/initiator_mock.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2022 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_spi/initiator_mock.h"

#include "gtest/gtest.h"
#include "pw_assert/check.h"

namespace pw::spi {

Status MockInitiator::WriteRead(ConstByteSpan tx_buffer, ByteSpan rx_buffer) {
PW_CHECK_INT_LT(expected_transaction_index_, expected_transactions_.size());

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();
PW_CHECK_INT_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::spi
95 changes: 95 additions & 0 deletions pw_spi/initiator_mock_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2022 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_spi/initiator_mock.h"

#include <array>
#include <span>

#include "gtest/gtest.h"
#include "pw_bytes/array.h"
#include "pw_bytes/span.h"

namespace pw::spi {
namespace {

TEST(Transaction, Read) {
constexpr auto kExpectRead1 = bytes::Array<1, 2, 3, 4, 5>();
constexpr auto kExpectRead2 = bytes::Array<3, 4, 5>();

auto expected_transactions = MakeExpectedTransactionArray(
{MockReadTransaction(OkStatus(), kExpectRead1),
MockReadTransaction(OkStatus(), kExpectRead2)});

MockInitiator mocked_spi(expected_transactions);

std::array<std::byte, kExpectRead1.size()> read1;
EXPECT_EQ(mocked_spi.WriteRead(ConstByteSpan(), read1), OkStatus());
EXPECT_TRUE(std::equal(
read1.begin(), read1.end(), kExpectRead1.begin(), kExpectRead1.end()));

std::array<std::byte, kExpectRead2.size()> read2;
EXPECT_EQ(mocked_spi.WriteRead(ConstByteSpan(), read2), OkStatus());
EXPECT_TRUE(std::equal(
read2.begin(), read2.end(), kExpectRead2.begin(), kExpectRead2.end()));

EXPECT_EQ(mocked_spi.Finalize(), OkStatus());
}

TEST(Transaction, Write) {
constexpr auto kExpectWrite1 = bytes::Array<1, 2, 3, 4, 5>();
constexpr auto kExpectWrite2 = bytes::Array<3, 4, 5>();

auto expected_transactions = MakeExpectedTransactionArray(
{MockWriteTransaction(OkStatus(), kExpectWrite1),
MockWriteTransaction(OkStatus(), kExpectWrite2)});

MockInitiator mocked_spi(expected_transactions);

EXPECT_EQ(mocked_spi.WriteRead(kExpectWrite1, ByteSpan()), OkStatus());

EXPECT_EQ(mocked_spi.WriteRead(kExpectWrite2, ByteSpan()), OkStatus());

EXPECT_EQ(mocked_spi.Finalize(), OkStatus());
}

TEST(Transaction, WriteRead) {
constexpr auto kExpectWrite1 = bytes::Array<1, 2, 3, 4, 5>();
constexpr auto kExpectRead1 = bytes::Array<1, 2>();

constexpr auto kExpectWrite2 = bytes::Array<3, 4, 5>();
constexpr auto kExpectRead2 = bytes::Array<3, 4>();

auto expected_transactions = MakeExpectedTransactionArray({
MockTransaction(OkStatus(), kExpectWrite1, kExpectRead1),
MockTransaction(OkStatus(), kExpectWrite2, kExpectRead2),
});

MockInitiator mocked_spi(expected_transactions);

std::array<std::byte, kExpectRead1.size()> read1;
EXPECT_EQ(mocked_spi.WriteRead(kExpectWrite1, read1), OkStatus());
EXPECT_TRUE(std::equal(
read1.begin(), read1.end(), kExpectRead1.begin(), kExpectRead1.end()));

std::array<std::byte, kExpectRead2.size()> read2;
EXPECT_EQ(mocked_spi.WriteRead(kExpectWrite2, read2), OkStatus());
EXPECT_TRUE(std::equal(
read2.begin(), read2.end(), kExpectRead2.begin(), kExpectRead2.end()));

EXPECT_EQ(mocked_spi.Finalize(), OkStatus());
}

} // namespace
} // namespace pw::spi
36 changes: 36 additions & 0 deletions pw_spi/public/pw_spi/chip_selector_mock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2022 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 <cstdint>

#include "pw_spi/chip_selector.h"
#include "pw_status/status.h"

namespace pw::spi {

class MockChipSelector : public ChipSelector {
public:
bool is_active() { return active_; }

private:
Status SetActive(bool active) override {
active_ = active;
return pw::OkStatus();
}
bool active_ = false;
};

} // namespace pw::spi
Loading

0 comments on commit 7cabfff

Please sign in to comment.