diff --git a/.gitlab/ci/host-test.yml b/.gitlab/ci/host-test.yml index 37747ac21b..de75b9df94 100644 --- a/.gitlab/ci/host-test.yml +++ b/.gitlab/ci/host-test.yml @@ -356,6 +356,13 @@ test_esp_event: - idf.py build - build/test_esp_event_host.elf +test_esp_timer_cxx: + extends: .host_test_template + script: + - cd ${IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer + - idf.py build + - build/test_esp_timer_cxx_host.elf + test_eh_frame_parser: extends: .host_test_template script: diff --git a/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt b/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt index 7e430b5d5f..20c8de477c 100644 --- a/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt +++ b/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt @@ -1,9 +1,16 @@ -idf_component_register(SRCS - "esp_exception.cpp" - "i2c_cxx.cpp" - "gpio_cxx.cpp" - "esp_event_api.cpp" - "esp_event_cxx.cpp" - "esp_timer_cxx.cpp" +idf_build_get_property(target IDF_TARGET) + +set(srcs "esp_timer_cxx.cpp" "esp_exception.cpp" "gpio_cxx.cpp") +set(requires "esp_timer" "driver") + +if(NOT ${target} STREQUAL "linux") + list(APPEND srcs + "i2c_cxx.cpp" + "esp_event_api.cpp" + "esp_event_cxx.cpp") + list(APPEND requires "esp_event") +endif() + +idf_component_register(SRCS ${srcs} INCLUDE_DIRS "include" - REQUIRES driver esp_event esp_timer) + REQUIRES ${requires}) diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/CMakeLists.txt b/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/CMakeLists.txt new file mode 100644 index 0000000000..2e0a04280f --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) + +list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/") +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}/examples/cxx/experimental/experimental_cpp_component/") +project(test_esp_timer_cxx_host) diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/README.md b/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/README.md new file mode 100644 index 0000000000..9612efa33e --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/README.md @@ -0,0 +1,36 @@ +| Supported Targets | Linux | +| ----------------- | ----- | + +# C++ ESPTimer test on Linux target + +This unit test tests basic functionality of the `ESPTimer` class. The test does not use mocks. Instead, it runs the whole implementation of the component on the Linux host. The test framework is CATCH. + +## Requirements + +* A Linux system +* The usual IDF requirements for Linux system, as described in the [Getting Started Guides](../../../../docs/en/get-started/index.rst). +* The host's gcc/g++ + +This application has been tested on Ubuntu 20.04 with `gcc` version *9.3.0*. + +## Build + +First, make sure that the target is set to Linux. Run `idf.py --preview set-target linux` if you are not sure. Then do a normal IDF build: `idf.py build`. + +## Run + +IDF monitor doesn't work yet for Linux. You have to run the app manually: + +```bash +build/test_esp_timer_cxx_host.elf +``` + +## Example Output + +Ideally, all tests pass, which is indicated by "All tests passed" in the last line: + +```bash +$ build/test_esp_timer_cxx_host.elf +=============================================================================== +All tests passed (9 assertions in 11 test cases) +``` diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/main/CMakeLists.txt b/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/main/CMakeLists.txt new file mode 100644 index 0000000000..ae9f501091 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "esp_timer_test.cpp" + INCLUDE_DIRS + "." + $ENV{IDF_PATH}/tools/catch + REQUIRES cmock esp_timer experimental_cpp_component) diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/main/esp_timer_test.cpp b/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/main/esp_timer_test.cpp new file mode 100644 index 0000000000..f379e6049c --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/main/esp_timer_test.cpp @@ -0,0 +1,196 @@ +/* ESP Timer C++ unit tests + + 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. +*/ +#define CATCH_CONFIG_MAIN + +#include +#include +#include "esp_err.h" +#include "esp_timer_cxx.hpp" + +#include "catch.hpp" + +extern "C" { +#include "Mockesp_timer.h" +} + +// 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; +using namespace idf::esp_timer; + +struct FixtureException : std::exception { + const char *what() const noexcept override { + return "CMock failed"; + } +}; + +struct TimerCreationFixture { + TimerCreationFixture(bool expect_stop = false) : out_handle(reinterpret_cast(1)) + { + if (!TEST_PROTECT()) { + throw FixtureException(); + } + esp_timer_create_ExpectAnyArgsAndReturn(ESP_OK); + esp_timer_create_ReturnThruPtr_out_handle(&out_handle); + if (expect_stop) { + esp_timer_stop_ExpectAndReturn(out_handle, ESP_OK); // implementation may always call stop + } else { + esp_timer_stop_IgnoreAndReturn(ESP_OK); // implementation may always call stop + } + esp_timer_delete_ExpectAndReturn(out_handle, ESP_OK); + } + + virtual ~TimerCreationFixture() + { + Mockesp_timer_Verify(); + } + + esp_timer_handle_t out_handle; +}; + +static void (*trigger_timer_callback)(void *data) = nullptr; + +esp_err_t cmock_timer_create_callback(const esp_timer_create_args_t* create_args, esp_timer_handle_t* out_handle, int cmock_num_calls) +{ + trigger_timer_callback = create_args->callback; + return ESP_OK; +} + +struct TimerCallbackFixture : public TimerCreationFixture { + TimerCallbackFixture(bool expect_stop = false) : TimerCreationFixture(expect_stop) + { + esp_timer_create_AddCallback(cmock_timer_create_callback); + } + + ~TimerCallbackFixture() + { + trigger_timer_callback = nullptr; + } +}; + +TEST_CASE("get_time works") +{ + esp_timer_get_time_ExpectAndReturn(static_cast(0xfeeddeadbeef)); + + CHECK(get_time() == std::chrono::microseconds(0xfeeddeadbeef)); +} + +TEST_CASE("get_next_alarm works") +{ + esp_timer_get_next_alarm_ExpectAndReturn(static_cast(47u)); + + CHECK(get_next_alarm() == std::chrono::microseconds(47u)); +} + +TEST_CASE("ESPTimer null function") +{ + CHECK_THROWS_AS(ESPTimer(nullptr), ESPException&); +} + +TEST_CASE("ESPTimer empty std::function") +{ + function nothing; + CHECK_THROWS_AS(ESPTimer(nothing, "test"), ESPException&); +} + +TEST_CASE("ESPTimer initializes and deletes itself") +{ + TimerCreationFixture fix; + + function timer_cb = [&]() { }; + + ESPTimer(timer_cb, "test"); +} + +TEST_CASE("ESPTimer start throws on invalid state failure") +{ + TimerCreationFixture fix; + esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_ERR_INVALID_STATE); + + function timer_cb = [&]() { }; + + ESPTimer timer(timer_cb); + + CHECK_THROWS_AS(timer.start(chrono::microseconds(5000)), ESPException&); +} + +TEST_CASE("ESPTimer start periodically throws on invalid state failure") +{ + TimerCreationFixture fix; + esp_timer_start_periodic_ExpectAndReturn(fix.out_handle, 5000, ESP_ERR_INVALID_STATE); + + function timer_cb = [&]() { }; + + ESPTimer timer(timer_cb); + + CHECK_THROWS_AS(timer.start_periodic(chrono::microseconds(5000)), ESPException&); +} + +TEST_CASE("ESPTimer stopp throws on invaid state failure") +{ + TimerCreationFixture fix; + + // Overriding stop part of the fixture + esp_timer_stop_StopIgnore(); + esp_timer_stop_IgnoreAndReturn(ESP_ERR_INVALID_STATE); + + function timer_cb = [&]() { }; + + ESPTimer timer(timer_cb); + + CHECK_THROWS_AS(timer.stop(), ESPException&); +} + +TEST_CASE("ESPTimer stops in destructor") +{ + TimerCreationFixture fix(true); + esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_OK); + + function timer_cb = [&]() { }; + + ESPTimer timer(timer_cb); + + timer.start(chrono::microseconds(5000)); +} + +TEST_CASE("ESPTimer stops correctly") +{ + TimerCreationFixture fix(true); + esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_OK); + + // Additional stop needed because stop is called in ESPTimer::stop and ~ESPTimer. + esp_timer_stop_ExpectAndReturn(fix.out_handle, ESP_OK); + + function timer_cb = [&]() { }; + + ESPTimer timer(timer_cb); + + timer.start(chrono::microseconds(5000)); + + timer.stop(); +} + +TEST_CASE("ESPTimer callback works") +{ + TimerCallbackFixture fix; + int flag = 0; + + function timer_cb = [&]() { flag = 47; }; + + ESPTimer timer(timer_cb); + + trigger_timer_callback(&timer); + + REQUIRE(trigger_timer_callback != nullptr); + CHECK(flag == 47); +} diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/sdkconfig.defaults b/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/sdkconfig.defaults new file mode 100644 index 0000000000..a057733348 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n +CONFIG_IDF_TARGET="linux" +CONFIG_CXX_EXCEPTIONS=y diff --git a/examples/cxx/experimental/experimental_cpp_component/include/esp_timer_cxx.hpp b/examples/cxx/experimental/experimental_cpp_component/include/esp_timer_cxx.hpp index b247226d57..a83eefaee8 100644 --- a/examples/cxx/experimental/experimental_cpp_component/include/esp_timer_cxx.hpp +++ b/examples/cxx/experimental/experimental_cpp_component/include/esp_timer_cxx.hpp @@ -18,6 +18,7 @@ #include #include +#include #include "esp_exception.hpp" #include "esp_timer.h" diff --git a/examples/cxx/experimental/experimental_cpp_component/test/test_esp_timer.cpp b/examples/cxx/experimental/experimental_cpp_component/test/test_esp_timer.cpp index e36ab58ba1..fb13f22a99 100644 --- a/examples/cxx/experimental/experimental_cpp_component/test/test_esp_timer.cpp +++ b/examples/cxx/experimental/experimental_cpp_component/test/test_esp_timer.cpp @@ -44,82 +44,6 @@ struct RefClock { } }; -TEST_CASE("ESPTimer null function", "[ESPTimer]") -{ - TEST_THROW(ESPTimer(nullptr), ESPException); -} - -TEST_CASE("ESPTimer empty std::function", "[ESPTimer]") -{ - function nothing; - TEST_THROW(ESPTimer(nothing, "test"), ESPException); -} - -TEST_CASE("ESPTimer starting twice throws", "[ESPTimer]") -{ - function timer_cb = [&]() { }; - - ESPTimer timer(timer_cb); - - timer.start(chrono::microseconds(5000)); - - TEST_THROW(timer.start(chrono::microseconds(5000)), ESPException); -} - -TEST_CASE("ESPTimer periodically starting twice throws", "[ESPTimer]") -{ - function timer_cb = [&]() { }; - - ESPTimer timer(timer_cb); - - timer.start_periodic(chrono::microseconds(5000)); - - TEST_THROW(timer.start_periodic(chrono::microseconds(5000)), ESPException); -} - -TEST_CASE("ESPTimer stopping non-started timer throws", "[ESPTimer]") -{ - function timer_cb = [&]() { }; - - ESPTimer timer(timer_cb); - - TEST_THROW(timer.stop(), ESPException); -} - -TEST_CASE("ESPTimer calls callback", "[ESPTimer]") -{ - bool called = false; - - function timer_cb = [&]() { - called = true; - }; - - ESPTimer timer(timer_cb); - - timer.start(chrono::microseconds(5000)); - - vTaskDelay(10 / portTICK_PERIOD_MS); - - TEST_ASSERT(called); -} - -TEST_CASE("ESPTimer periodically calls callback", "[ESPTimer]") -{ - size_t called = 0; - - function timer_cb = [&]() { - called++; - }; - - ESPTimer timer(timer_cb); - - timer.start_periodic(chrono::microseconds(2000)); - - vTaskDelay(10 / portTICK_PERIOD_MS); - - TEST_ASSERT(called >= 4u); -} - TEST_CASE("ESPTimer produces correct delay", "[ESPTimer]") { int64_t t_end; diff --git a/tools/mocks/esp_timer/CMakeLists.txt b/tools/mocks/esp_timer/CMakeLists.txt new file mode 100644 index 0000000000..c9dac893d2 --- /dev/null +++ b/tools/mocks/esp_timer/CMakeLists.txt @@ -0,0 +1,9 @@ +# NOTE: This kind of mocking currently works on Linux targets only. +# On Espressif chips, too many dependencies are missing at the moment. +message(STATUS "building ESP TIMER MOCKS") + +idf_component_get_property(original_esp_timer_dir esp_timer COMPONENT_OVERRIDEN_DIR) + +idf_component_mock(INCLUDE_DIRS "${original_esp_timer_dir}/include" + REQUIRES esp_common + MOCK_HEADER_FILES ${original_esp_timer_dir}/include/esp_timer.h) diff --git a/tools/mocks/esp_timer/mock/mock_config.yaml b/tools/mocks/esp_timer/mock/mock_config.yaml new file mode 100644 index 0000000000..596255b0ce --- /dev/null +++ b/tools/mocks/esp_timer/mock/mock_config.yaml @@ -0,0 +1,9 @@ + :cmock: + :plugins: + - expect + - expect_any_args + - return_thru_ptr + - array + - ignore + - ignore_arg + - callback