[cxx]: simple spi master class

* spi cxx unit test (CATCH-based, on host)
* added portmacro.h to driver mocking
* added simple testing app to write/read SPI,
  using an MPU9250
This commit is contained in:
Jakob Hasse
2021-01-14 13:52:36 +08:00
parent c499fe5fa5
commit 7efb01846f
35 changed files with 2154 additions and 52 deletions

View File

@@ -1,21 +1,23 @@
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
//
// 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
// http://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.
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0
*
* This example code is in the Public Domain (or CC0 licensed, at your option.)
*
* Unless required by applicable law or agreed to in writing, this
* software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied.
*/
#include "catch.hpp"
#include "gpio_cxx.hpp"
#include "driver/spi_master.h"
#include "spi_cxx.hpp"
extern "C" {
#include "Mockgpio.h"
#include "Mockspi_master.h"
#include "Mockspi_common.h"
}
static const idf::GPIONum VALID_GPIO(18);
@@ -45,7 +47,7 @@ public:
* Helper macro for setting up a test protect call for CMock.
*
* This macro should be used at the beginning of any test cases
* which use generated CMock mock functions.
* that uses generated CMock mock functions.
* This is necessary because CMock uses longjmp which screws up C++ stacks and
* also the CATCH mechanisms.
*
@@ -61,18 +63,236 @@ public:
} \
while (0)
struct GPIOFixture {
GPIOFixture(idf::GPIONum gpio_num = idf::GPIONum(18), gpio_mode_t mode = GPIO_MODE_OUTPUT) : num(gpio_num)
struct CMockFixture {
CMockFixture()
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(num.get_num()), ESP_OK); gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(num.get_num()), mode, ESP_OK);
}
~GPIOFixture()
~CMockFixture()
{
// Verify that all expected methods have been called.
Mockgpio_Verify();
Mockspi_master_Verify();
Mockspi_common_Verify();
}
};
struct GPIOFixture : public CMockFixture {
GPIOFixture(idf::GPIONum gpio_num = idf::GPIONum(18), gpio_mode_t mode = GPIO_MODE_OUTPUT)
: CMockFixture(), num(gpio_num)
{
gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(num.get_num()), ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(num.get_num()), mode, ESP_OK);
}
idf::GPIONum num;
};
struct SPIFix;
struct SPIDevFix;
struct SPITransactionDescriptorFix;
struct SPITransactionTimeoutFix;
struct SPITransactionFix;
static SPIFix *g_fixture;
static SPIDevFix *g_dev_fixture;
static SPITransactionDescriptorFix *g_trans_desc_fixture;
static SPITransactionTimeoutFix *g_trans_timeout_fixture;
static SPITransactionFix *g_trans_fixture;
struct SPIFix : public CMockFixture {
SPIFix(spi_host_device_t host_id = spi_host_device_t(1),
uint32_t mosi = 1,
uint32_t miso = 2,
uint32_t sclk = 3) : CMockFixture(), bus_config() {
bus_config.mosi_io_num = mosi;
bus_config.miso_io_num = miso;
bus_config.sclk_io_num = sclk;
bus_config.quadwp_io_num = -1;
bus_config.quadhd_io_num = -1;
spi_bus_initialize_ExpectWithArrayAndReturn(host_id, &bus_config, 1, spi_common_dma_t::SPI_DMA_CH_AUTO, ESP_OK);
spi_bus_free_ExpectAnyArgsAndReturn(ESP_OK);
g_fixture = this;
}
~SPIFix() {
g_fixture = nullptr;
}
spi_bus_config_t bus_config;
};
struct QSPIFix : public SPIFix {
QSPIFix(spi_host_device_t host_id = spi_host_device_t(1),
uint32_t mosi = 1,
uint32_t miso = 2,
uint32_t sclk = 3,
uint32_t wp = 4,
uint32_t hd = 5) : SPIFix(host_id, mosi, miso, sclk)
{
bus_config.quadwp_io_num = wp;
bus_config.quadhd_io_num = hd;
}
};
enum class CreateAnd {
FAIL,
SUCCEED,
IGNORE
};
struct SPIDevFix {
SPIDevFix(CreateAnd flags)
: dev_handle(reinterpret_cast<spi_device_handle_t>(47)),
dev_config()
{
dev_config.spics_io_num = 4;
if (flags == CreateAnd::FAIL) {
spi_bus_add_device_ExpectAnyArgsAndReturn(ESP_FAIL);
} else if (flags == CreateAnd::IGNORE) {
spi_bus_add_device_IgnoreAndReturn(ESP_OK);
spi_bus_remove_device_IgnoreAndReturn(ESP_OK);
} else {
spi_bus_add_device_AddCallback(add_dev_cb);
spi_bus_add_device_ExpectAnyArgsAndReturn(ESP_OK);
spi_bus_remove_device_ExpectAndReturn(dev_handle, ESP_OK);
}
g_dev_fixture = this;
}
~SPIDevFix()
{
spi_bus_add_device_AddCallback(nullptr);
g_dev_fixture = nullptr;
}
spi_device_handle_t dev_handle;
spi_device_interface_config_t dev_config;
static esp_err_t add_dev_cb(spi_host_device_t host_id,
const spi_device_interface_config_t* dev_config,
spi_device_handle_t* handle,
int cmock_num_calls)
{
SPIDevFix *fix = static_cast<SPIDevFix*>(g_dev_fixture);
*handle = fix->dev_handle;
fix->dev_config = *dev_config;
return ESP_OK;
}
};
struct SPITransactionFix {
SPITransactionFix(esp_err_t get_trans_return = ESP_OK) : get_transaction_return(get_trans_return)
{
spi_device_queue_trans_AddCallback(queue_trans_cb);
spi_device_get_trans_result_AddCallback(get_trans_result_cb);
spi_device_queue_trans_ExpectAnyArgsAndReturn(ESP_OK);
spi_device_get_trans_result_ExpectAnyArgsAndReturn(get_trans_return);
g_trans_fixture = this;
}
~SPITransactionFix()
{
spi_device_get_trans_result_AddCallback(nullptr);
spi_device_queue_trans_AddCallback(nullptr);
g_trans_fixture = nullptr;
}
static esp_err_t queue_trans_cb(spi_device_handle_t handle,
spi_transaction_t* trans_desc,
TickType_t ticks_to_wait,
int cmock_num_calls)
{
SPITransactionFix *fix = static_cast<SPITransactionFix*> (g_trans_fixture);
fix->orig_trans = trans_desc;
return ESP_OK;
}
static esp_err_t get_trans_result_cb(spi_device_handle_t handle,
spi_transaction_t** trans_desc,
TickType_t ticks_to_wait,
int cmock_num_calls)
{
SPITransactionFix *fix = static_cast<SPITransactionFix*> (g_trans_fixture);
*trans_desc = fix->orig_trans;
return fix->get_transaction_return;
}
esp_err_t get_transaction_return;
spi_transaction_t *orig_trans;
};
struct SPITransactionDescriptorFix {
SPITransactionDescriptorFix(size_t size = 1, bool ignore_handle = false, TickType_t wait_time = portMAX_DELAY)
: size(size), handle(reinterpret_cast<spi_device_handle_t>(0x01020304))
{
spi_device_queue_trans_AddCallback(queue_trans_cb);
spi_device_get_trans_result_AddCallback(get_trans_result_cb);
spi_device_acquire_bus_ExpectAndReturn(handle, portMAX_DELAY, ESP_OK);
if (ignore_handle) {
spi_device_acquire_bus_IgnoreArg_device();
}
spi_device_queue_trans_ExpectAndReturn(handle, nullptr, 0, ESP_OK);
spi_device_queue_trans_IgnoreArg_trans_desc();
if (ignore_handle) {
spi_device_queue_trans_IgnoreArg_handle();
}
spi_device_get_trans_result_ExpectAndReturn(handle, nullptr, wait_time, ESP_OK);
spi_device_get_trans_result_IgnoreArg_trans_desc();
if (ignore_handle) {
spi_device_get_trans_result_IgnoreArg_handle();
}
spi_device_release_bus_ExpectAnyArgs();
g_trans_desc_fixture = this;
}
~SPITransactionDescriptorFix()
{
spi_device_get_trans_result_AddCallback(nullptr);
spi_device_queue_trans_AddCallback(nullptr);
g_trans_desc_fixture = nullptr;
}
static esp_err_t queue_trans_cb(spi_device_handle_t handle,
spi_transaction_t* trans_desc,
TickType_t ticks_to_wait,
int cmock_num_calls)
{
SPITransactionDescriptorFix *fix = static_cast<SPITransactionDescriptorFix*> (g_trans_desc_fixture);
fix->orig_trans = trans_desc;
return ESP_OK;
}
static esp_err_t get_trans_result_cb(spi_device_handle_t handle,
spi_transaction_t** trans_desc,
TickType_t ticks_to_wait,
int cmock_num_calls)
{
SPITransactionDescriptorFix *fix = static_cast<SPITransactionDescriptorFix*> (g_trans_desc_fixture);
for (int i = 0; i < fix->size; i++) {
static_cast<uint8_t*>(fix->orig_trans->rx_buffer)[i] = fix->rx_data[i];
}
*trans_desc = fix->orig_trans;
return ESP_OK;
}
size_t size;
spi_transaction_t *orig_trans;
spi_device_handle_t handle;
std::vector<uint8_t> tx_data;
std::vector<uint8_t> rx_data;
};

View File

@@ -10,6 +10,7 @@
#include <stdio.h>
#include "esp_err.h"
#include "unity.h"
#include "freertos/portmacro.h"
#include "gpio_cxx.hpp"
#include "test_fixtures.hpp"

View File

@@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
idf_build_set_property(COMPILE_DEFINITIONS "-DNO_DEBUG_STORAGE" APPEND)
# Overriding components which should be mocked
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/")
# Including experimental component here because it's outside IDF's main component directory
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/")
project(test_spi_cxx_host)

View File

@@ -0,0 +1,8 @@
| Supported Targets | Linux |
| ----------------- | ----- |
# Build
`idf.py build` (sdkconfig.defaults sets the linux target by default)
# Run
`build/host_spi_cxx_test.elf`

View File

@@ -0,0 +1,9 @@
idf_component_get_property(cpp_component experimental_cpp_component COMPONENT_DIR)
idf_component_register(SRCS "spi_cxx_test.cpp"
INCLUDE_DIRS
"."
"${cpp_component}/host_test/fixtures"
"${cpp_component}/private_include"
$ENV{IDF_PATH}/tools/catch
REQUIRES cmock driver experimental_cpp_component)

View File

@@ -0,0 +1,452 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0
*
* This test code is in the Public Domain (or CC0 licensed, at your option.)
*
* Unless required by applicable law or agreed to in writing, this
* software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied.
*/
#define CATCH_CONFIG_MAIN
#include <stdio.h>
#include "unity.h"
#include "freertos/portmacro.h"
#include "spi_host_cxx.hpp"
#include "spi_host_private_cxx.hpp"
#include "system_cxx.hpp"
#include "test_fixtures.hpp"
#include "catch.hpp"
// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
const char *esp_err_to_name(esp_err_t code) {
return "host_test error";
}
using namespace std;
using namespace idf;
TEST_CASE("SPITransferSize basic construction")
{
SPITransferSize transfer_size_0(0);
CHECK(0 == transfer_size_0.get_value());
SPITransferSize transfer_size_1(47);
CHECK(47 == transfer_size_1.get_value());
SPITransferSize transfer_size_default = SPITransferSize::default_size();
CHECK(0 == transfer_size_default.get_value());
}
TEST_CASE("SPI gpio numbers work correctly")
{
GPIONum gpio_num_0(19);
MOSI mosi_0(18);
MOSI mosi_1(gpio_num_0.get_num());
MOSI mosi_2(mosi_0);
CHECK(mosi_0 != mosi_1);
CHECK(mosi_2 == mosi_0);
CHECK(mosi_2.get_num() == 18u);
}
TEST_CASE("SPI_DMAConfig valid")
{
CHECK(SPI_DMAConfig::AUTO().get_num() == spi_common_dma_t::SPI_DMA_CH_AUTO);
CHECK(SPI_DMAConfig::DISABLED().get_num() == spi_common_dma_t::SPI_DMA_DISABLED);
}
TEST_CASE("SPINum invalid argument")
{
CHECK_THROWS_AS(SPINum(-1), SPIException&);
uint32_t host_raw = spi_host_device_t::SPI_HOST_MAX;
CHECK_THROWS_AS(SPINum host(host_raw), SPIException&);
}
TEST_CASE("Master init failure")
{
CMockFixture cmock_fix;
spi_bus_initialize_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(SPIMaster master(SPINum(SPI2_HOST), MOSI(1), MISO(2), SCLK(3)), SPIException&);
}
TEST_CASE("Master invalid state")
{
CMockFixture cmock_fix;
spi_bus_initialize_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_STATE);
CHECK_THROWS_AS(SPIMaster master(SPINum(SPI2_HOST), MOSI(1), MISO(2), SCLK(3)), SPIException&);
}
TEST_CASE("build master")
{
SPIFix fix;
SPIMaster master(SPINum(SPI2_HOST),
MOSI(fix.bus_config.mosi_io_num),
MISO(fix.bus_config.miso_io_num),
SCLK(fix.bus_config.sclk_io_num));
}
TEST_CASE("build QSPI master")
{
QSPIFix fix;
SPIMaster master(SPINum(SPI2_HOST),
MOSI(fix.bus_config.mosi_io_num),
MISO(fix.bus_config.miso_io_num),
SCLK(fix.bus_config.sclk_io_num),
QSPIWP(fix.bus_config.quadwp_io_num),
QSPIHD(fix.bus_config.quadhd_io_num));
}
TEST_CASE("Master build device")
{
SPIFix fix;
SPIDevFix dev_fix(CreateAnd::SUCCEED);
SPIMaster master(SPINum(SPI2_HOST),
MOSI(fix.bus_config.mosi_io_num),
MISO(fix.bus_config.miso_io_num),
SCLK(fix.bus_config.sclk_io_num));
master.create_dev(CS(4), Frequency::MHz(1));
}
TEST_CASE("SPIDeviceHandle throws on driver error")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::FAIL);
CHECK_THROWS_AS(SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)), SPIException&);
}
TEST_CASE("SPIDeviceHandle succeed")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::SUCCEED);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
}
TEST_CASE("SPIDevice succeed")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::SUCCEED);
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
}
TEST_CASE("SPI transaction empty data throws")
{
CHECK_THROWS_AS(SPITransactionDescriptor transaction({}, reinterpret_cast<SPIDeviceHandle*>(4747)), SPIException&);
}
TEST_CASE("SPI transaction device handle nullptr throws")
{
CHECK_THROWS_AS(SPITransactionDescriptor transaction({47}, nullptr), SPIException&);
}
TEST_CASE("SPI transaction not started wait_for")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
CHECK_THROWS_AS(transaction.wait_for(std::chrono::milliseconds(47)), SPITransferException&);
}
TEST_CASE("SPI transaction not started wait")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
CHECK_THROWS_AS(transaction.wait(), SPITransferException&);
}
TEST_CASE("SPI transaction not started get")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
CHECK_THROWS_AS(transaction.get(), SPITransferException&);
}
TEST_CASE("SPI transaction wait_for timeout")
{
CMockFixture cmock_fix;
SPITransactionFix transaction_fix(ESP_ERR_TIMEOUT);
SPIDevFix dev_fix(CreateAnd::IGNORE);
spi_device_acquire_bus_IgnoreAndReturn(ESP_OK);
spi_device_release_bus_Ignore();
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
transaction.start();
CHECK(transaction.wait_for(std::chrono::milliseconds(47)) == false);
// We need to finish the transaction, otherwise it goes out of scope without finishing and cleaning up the
// allocated transaction descriptor.
transaction_fix.get_transaction_return = ESP_OK;
spi_device_get_trans_result_ExpectAnyArgsAndReturn(ESP_OK);
transaction.wait();
}
TEST_CASE("SPI transaction one byte")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix fix(1, true);
SPIDevFix dev_fix(CreateAnd::IGNORE);
fix.rx_data = {0xA6};
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
transaction.start();
auto out_data = transaction.get();
CHECK(1 * 8 == fix.orig_trans->length);
CHECK(47 == ((uint8_t*) fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(0xA6 == out_data[0]);
}
TEST_CASE("SPI transaction two byte")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix fix(2, true);
SPIDevFix dev_fix(CreateAnd::IGNORE);
fix.rx_data = {0xA6, 0xA7};
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47, 48}, &handle);
transaction.start();
auto out_data = transaction.get();
CHECK(fix.size * 8 == fix.orig_trans->length);
CHECK(47 == ((uint8_t*) fix.orig_trans->tx_buffer)[0]);
CHECK(48 == ((uint8_t*) fix.orig_trans->tx_buffer)[1]);
REQUIRE(out_data.begin() != out_data.end());
REQUIRE(out_data.size() == 2);
CHECK(0xA6 == out_data[0]);
CHECK(0xA7 == out_data[1]);
}
TEST_CASE("SPI transaction future")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47});
vector<uint8_t> out_data = result.get();
CHECK(1 * 8 == trans_fix.orig_trans->length);
CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(out_data.size() == 1);
CHECK(0xA6 == out_data[0]);
}
TEST_CASE("SPI transaction with pre_callback")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool pre_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47}, [&] (void *user) { pre_cb_called = true; });
vector<uint8_t> out_data = result.get();
SPITransactionDescriptor *transaction = reinterpret_cast<SPITransactionDescriptor*>(trans_fix.orig_trans->user);
dev_fix.dev_config.pre_cb(trans_fix.orig_trans);
CHECK(true == pre_cb_called);
CHECK(1 * 8 == trans_fix.orig_trans->length);
CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(out_data.size() == 1);
CHECK(0xA6 == out_data[0]);
}
TEST_CASE("SPI transaction with post_callback")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool post_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47}, [&] (void *user) { }, [&] (void *user) { post_cb_called = true; });
vector<uint8_t> out_data = result.get();
dev_fix.dev_config.post_cb(trans_fix.orig_trans);
CHECK(true == post_cb_called);
CHECK(1 * 8 == trans_fix.orig_trans->length);
CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(out_data.size() == 1);
CHECK(0xA6 == out_data[0]);
}
TEST_CASE("SPI transaction data routed to pre callback")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool pre_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47},
[&] (void *user) { *(static_cast<bool*>(user)) = true; },
[&] (void *user) { },
&pre_cb_called);
result.get();
dev_fix.dev_config.pre_cb(trans_fix.orig_trans);
CHECK(true == pre_cb_called);
}
TEST_CASE("SPI transaction data routed to post callback")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool post_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47},
[&] (void *user) { },
[&] (void *user) { *(static_cast<bool*>(user)) = true; },
&post_cb_called);
result.get();
dev_fix.dev_config.post_cb(trans_fix.orig_trans);
CHECK(true == post_cb_called);
}
TEST_CASE("SPI two transactions")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool pre_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
std::function<void(void *)> pre_callback = [&] (void *user) {
pre_cb_called = true;
};
auto result = dev.transfer({47}, pre_callback);
vector<uint8_t> out_data = result.get();
dev_fix.dev_config.pre_cb(trans_fix.orig_trans);
CHECK(true == pre_cb_called);
CHECK(1 * 8 == trans_fix.orig_trans->length);
CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(out_data.size() == 1);
CHECK(0xA6 == out_data[0]);
// preparing the second transfer
pre_cb_called = false;
spi_device_acquire_bus_ExpectAndReturn(trans_fix.handle, portMAX_DELAY, ESP_OK);
spi_device_acquire_bus_IgnoreArg_device();
spi_device_queue_trans_ExpectAndReturn(trans_fix.handle, nullptr, 0, ESP_OK);
spi_device_queue_trans_IgnoreArg_trans_desc();
spi_device_queue_trans_IgnoreArg_handle();
spi_device_get_trans_result_ExpectAndReturn(trans_fix.handle, nullptr, portMAX_DELAY, ESP_OK);
spi_device_get_trans_result_IgnoreArg_trans_desc();
spi_device_get_trans_result_IgnoreArg_handle();
spi_device_release_bus_Ignore();
result = dev.transfer({47}, pre_callback);
result.get();
dev_fix.dev_config.pre_cb(trans_fix.orig_trans);
CHECK(true == pre_cb_called);
}
TEST_CASE("SPIFuture invalid after default construction")
{
SPIFuture future;
CHECK(false == future.valid());
}
TEST_CASE("SPIFuture valid")
{
CMOCK_SETUP();
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
shared_ptr<SPITransactionDescriptor> trans(new SPITransactionDescriptor(std::vector<uint8_t>(47), &handle));
SPIFuture future(trans);
CHECK(true == future.valid());
}
TEST_CASE("SPIFuture wait_for timeout")
{
CMockFixture cmock_fix;
SPITransactionFix transaction_fix(ESP_ERR_TIMEOUT);
SPIDevFix dev_fix(CreateAnd::IGNORE);
spi_device_acquire_bus_IgnoreAndReturn(ESP_OK);
spi_device_release_bus_Ignore();
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
shared_ptr<SPITransactionDescriptor> transaction(new SPITransactionDescriptor(std::vector<uint8_t>(47), &handle));
SPIFuture future(transaction);
transaction->start();
CHECK(future.wait_for(std::chrono::milliseconds(47)) == std::future_status::timeout);
// We need to finish the transaction, otherwise it goes out of scope without finishing and cleaning up the
// allocated transaction descriptor.
transaction_fix.get_transaction_return = ESP_OK;
spi_device_get_trans_result_ExpectAnyArgsAndReturn(ESP_OK);
future.wait();
}
TEST_CASE("SPIFuture wait_for on SPIFuture")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true, 20);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47});
CHECK(result.wait_for(std::chrono::milliseconds(20)) == std::future_status::ready);
}
TEST_CASE("SPIFuture wait on SPIFuture")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47});
result.wait();
vector<uint8_t> out_data = result.get();
CHECK(out_data.size() == 1);
}

View File

@@ -0,0 +1,3 @@
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
CONFIG_IDF_TARGET="linux"
CONFIG_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,34 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
idf_component_set_property(driver USE_MOCK 1)
# Overriding components which should be mocked
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
# Including experimental component here because it's outside IDF's main component directory
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/")
project(test_system_cxx_host)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build"
COMMAND lcov --capture --directory . --output-file coverage.info
COMMENT "Create coverage report"
)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage_report/"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build"
COMMAND genhtml coverage.info --output-directory coverage_report/
COMMENT "Turn coverage report into html-based visualization"
)
add_custom_target(coverage
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build"
DEPENDS "coverage_report/"
)

View File

@@ -0,0 +1,8 @@
| Supported Targets | Linux |
| ----------------- | ----- |
# Build
`idf.py build` (sdkconfig.defaults sets the linux target by default)
# Run
`build/system_cxx_host_test.elf`

View File

@@ -0,0 +1,12 @@
idf_component_get_property(cpp_component experimental_cpp_component COMPONENT_DIR)
idf_component_register(SRCS "system_cxx_test.cpp"
"${cpp_component}/esp_exception.cpp"
INCLUDE_DIRS
"."
"${cpp_component}/include"
$ENV{IDF_PATH}/tools/catch
REQUIRES driver)
target_compile_options(${COMPONENT_LIB} PUBLIC --coverage)
target_link_libraries(${COMPONENT_LIB} --coverage)

View File

@@ -0,0 +1,95 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0
*
* This test code is in the Public Domain (or CC0 licensed, at your option.)
*
* Unless required by applicable law or agreed to in writing, this
* software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied.
*/
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include "system_cxx.hpp"
// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
const char *esp_err_to_name(esp_err_t code) {
return "test";
}
using namespace std;
using namespace idf;
TEST_CASE("Frequency invalid")
{
CHECK_THROWS_AS(Frequency(0), ESPException&);
}
TEST_CASE("Frequency constructors correct")
{
Frequency f0(440);
CHECK(440 == f0.get_value());
Frequency f1 = Frequency::Hz(440);
CHECK(440 == f1.get_value());
Frequency f2 = Frequency::KHz(440);
CHECK(440000 == f2.get_value());
Frequency f3 = Frequency::MHz(440);
CHECK(440000000 == f3.get_value());
}
TEST_CASE("Frequency op ==")
{
Frequency f0(440);
Frequency f1(440);
CHECK(f1 == f0);
}
TEST_CASE("Frequency op !=")
{
Frequency f0(440);
Frequency f1(441);
CHECK(f1 != f0);
}
TEST_CASE("Frequency op >")
{
Frequency f0(440);
Frequency f1(441);
Frequency f2(440);
CHECK(f1 > f0);
CHECK(!(f0 > f1));
CHECK(!(f0 > f2));
}
TEST_CASE("Frequency op <")
{
Frequency f0(440);
Frequency f1(441);
Frequency f2(440);
CHECK(f0 < f1);
CHECK(!(f1 < f0));
CHECK(!(f0 < f2));
}
TEST_CASE("Frequency op >=")
{
Frequency f0(440);
Frequency f1(441);
Frequency f2(440);
CHECK (f1 >= f0);
CHECK(!(f0 >= f1));
CHECK (f0 >= f2);
}
TEST_CASE("Frequency op <=")
{
Frequency f0(440);
Frequency f1(441);
Frequency f2(440);
CHECK (f0 <= f1);
CHECK(!(f1 <= f0));
CHECK (f0 <= f2);
}

View File

@@ -0,0 +1,3 @@
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
CONFIG_IDF_TARGET="linux"
CONFIG_CXX_EXCEPTIONS=y