Skip to content

Commit

Permalink
Merge pull request #238 from espressif/feature/catch2
Browse files Browse the repository at this point in the history
add Catch2 unit test library wrapper
  • Loading branch information
igrr authored Sep 18, 2023
2 parents 07871a4 + da1c0f7 commit 29d3182
Show file tree
Hide file tree
Showing 26 changed files with 422 additions and 1 deletion.
14 changes: 14 additions & 0 deletions .build-test-rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,17 @@ esp_jpeg/examples/get_started:
enable:
- if: IDF_VERSION_MAJOR > 4 and IDF_TARGET in ["esp32", "esp32s2", "esp32s3"]
reason: Example depends on BSP, which is supported only for IDF >= 5.0 and limited targets

catch2/examples/catch2-test:
enable:
- if: INCLUDE_DEFAULT == 1 or IDF_TARGET == "linux"
disable:
- if: IDF_VERSION_MAJOR < 5
reason: Example relies on WHOLE_ARCHIVE component property which was introduced in IDF v5.0
- if: IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR < 1 and IDF_TARGET == "linux"
reason: Docker container for release/v5.0 doesn't contain a C++ compiler

catch2/examples/catch2-console:
disable:
- if: IDF_VERSION_MAJOR < 5
reason: Example relies on WHOLE_ARCHIVE component property which was introduced in IDF v5.0
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ body:
multiple: false
options:
- bdc_motor
- catch2
- cbor
- coap
- coremark
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/upload_component.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
#
directories: >
bdc_motor;
catch2;
cbor;
ccomp_timer;
coap;
Expand Down
12 changes: 12 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
[submodule "coremark/coremark"]
path = coremark/coremark
url = https://github.com/eembc/coremark.git

[submodule "freetype/freetype"]
path = freetype/freetype
url = https://github.com/freetype/freetype.git
Expand All @@ -109,3 +110,14 @@
sbom-url = https://github.com/freetype/freetype.git
sbom-description = FreeType is a software font engine
sbom-hash = de8b92dd7ec634e9e2b25ef534c54a3537555c11

[submodule "catch2/Catch2"]
path = catch2/Catch2
url = https://github.com/catchorg/Catch2.git
sbom-version = 3.4.0
sbom-cpe = cpe:2.3:a:catchorg:catch2:{}:*:*:*:*:*:*:*
sbom-supplier = Organization: catchorg <https://github.com/catchorg>
sbom-url = https://github.com/catchorg/Catch2
sbom-description = A modern, C++-native, test framework for unit-tests, TDD and BDD - using C++14, C++17 and later
sbom-hash = 6e79e682b726f524310d55dec8ddac4e9c52fb5f

25 changes: 25 additions & 0 deletions catch2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
idf_component_register(SRCS cmd_catch2.cpp
INCLUDE_DIRS include)

set(CATCH_CONFIG_NO_POSIX_SIGNALS 1 CACHE BOOL OFF FORCE)
add_subdirectory(Catch2)

target_link_libraries(${COMPONENT_LIB} PUBLIC Catch2::Catch2)
get_target_property(catch_target Catch2::Catch2 ALIASED_TARGET)

# Silence a warning in catch_exception_translator_registry.cpp
target_compile_options(${catch_target} PRIVATE -Wno-unused-function)

# Link to pthreads to avoid issues with STL headers
idf_build_get_property(target IDF_TARGET)
if(NOT target STREQUAL "linux")
target_link_libraries(${catch_target} PUBLIC idf::pthread)
endif()

# If console component is present in the build, include the console
# command feature.
idf_build_get_property(build_components BUILD_COMPONENTS)
if("console" IN_LIST build_components)
target_compile_definitions(${COMPONENT_LIB} PRIVATE WITH_CONSOLE)
target_link_libraries(${COMPONENT_LIB} PUBLIC idf::console)
endif()
1 change: 1 addition & 0 deletions catch2/Catch2
Submodule Catch2 added at 6e79e6
23 changes: 23 additions & 0 deletions catch2/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
94 changes: 94 additions & 0 deletions catch2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Catch2 unit testing library

This component is an ESP-IDF wrapper around [Catch2 unit testing library](https://github.com/catchorg/Catch2).

Tests written using this component can be executed both on real hardware and on the host.

## Using Catch2 in ESP-IDF

### Example

To get started with Catch2 quickly, use the example provided along with this component:

```bash
idf.py create-project-from-example "espressif/catch:catch2-test"
cd catch2-test
idf.py set-target esp32
idf.py build flash monitor
```

The example can also be used on host (Linux):
```bash
idf.py --preview set-target linux
idf.py build monitor
```

### Writing tests

Tests and assertions are written as usual for Catch2, refer to the [official documentation](https://github.com/catchorg/Catch2/tree/devel/docs) for more details. Here is a simple example:

```c++
#include <catch2/catch_test_macros.hpp>

TEST_CASE("Test case 1")
{
REQUIRE(1 == 1);
}
```
To ensure the test cases are linked into the application, use `WHOLE_ARCHIVE` argument when calling `idf_component_register()` in CMake. For example:
```cmake
idf_component_register(SRCS "test_main.cpp"
"test_cases.cpp"
INCLUDE_DIRS "."
WHOLE_ARCHIVE)
```

### Enabling C++ exceptions

Catch2 relies on C++ exceptions for assertion handling. To get most benefits out of Catch2, it is recommended to have exceptions enabled in the project (`CONFIG_COMPILER_CXX_EXCEPTIONS=y`).

### Stack size

Catch2 uses significant amount of stack space — around 8kB, plus the stack space used by the test cases themselves. Therefore it is necessary to increase the stack size of the `main` task using the `CONFIG_ESP_MAIN_TASK_STACK_SIZE` option, or to invoke Catch from a new task with sufficient stack size. It is also recommended to keep `CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK` or `CONFIG_ESP_SYSTEM_HW_STACK_GUARD` options enabled to detect stack overflows.

### Invoking the test runner

Catch2 test framework can implement the application entry point (`main(int, char**)`) which calls the test runner. This functionality is typically used via the `CATCH_CONFIG_MAIN` macro. However ESP-IDF applications use `app_main(void)` function as an entry point, so the approach with `CATCH_CONFIG_MAIN` doesn't work.


Instead, invoke Catch2 in your `app_main` as follows:

```c++
#include <catch2/catch_session.hpp>

extern "C" void app_main(void)
{
// prepare command line arguments to Catch2
const int argc = 1;
const char* argv[2] = {"target_test_main", NULL};

// run the tests
int result = Catch::Session().run(argc, argv);
// ... handle the result
```
### Integration with ESP-IDF `console` component
This component provides a function to register an ESP-IDF console command to invoke Catch2 test cases:
```c++
esp_err_t register_catch2(const char* cmd_name);
```

This function registers a command with the specified name (for example, "test") with ESP-IDF `console` component. The command passes all the arguments to Catch2 test runner. This makes it possible to invoke tests from an interactive console running on an ESP chip.

To try this functionality, use `catch2-console` example:

```bash
idf.py create-project-from-example "espressif/catch:catch2-console"
cd catch2-console
idf.py set-target esp32
idf.py build flash monitor
```
33 changes: 33 additions & 0 deletions catch2/cmd_catch2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: BSL-1.0
* Note: same license as Catch2
*/
#if WITH_CONSOLE
#include "Catch2/src/catch2/catch_config.hpp"
#include "esp_console.h"
#include "catch2/catch_session.hpp"
#include "cmd_catch2.h"

static int cmd_catch2(int argc, char **argv)
{
static auto session = Catch::Session();
Catch::ConfigData configData;
session.useConfigData(configData);
return session.run(argc, argv);
}

extern "C" esp_err_t register_catch2(const char *cmd_name)
{
const esp_console_cmd_t cmd = {
.command = cmd_name,
.help = "Run tests",
.hint = NULL,
.func = &cmd_catch2,
.argtable = NULL
};
return esp_console_cmd_register(&cmd);
}

#endif // WITH_CONSOLE
7 changes: 7 additions & 0 deletions catch2/examples/catch2-console/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.16)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)

project(catch2-console)

23 changes: 23 additions & 0 deletions catch2/examples/catch2-console/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Catch2 console example

This example shows how to combine Catch2 test framework with ESP-IDF `console` component.

In this example, you can execute test cases from an interactive console on an ESP chip.

## Using the example

To run the example, build and flash the project as usual. For example, with an ESP32 chip:

```bash
idf.py set-target esp32
idf.py build flash monitor
```

In the console, use `test` command to invoke Catch2. `test` accepts the same command line arguments as Catch2 tests on the host:

- `test -h` — prints command line argument reference
- `test --list-tests` — lists all the registered tests
- `test` — runs all the registered tests
- `test <test name|pattern|tags>` — runs specific tests

[See Catch2 documentation](https://github.com/catchorg/Catch2/blob/devel/docs/command-line.md) for the complete command line argument reference.
5 changes: 5 additions & 0 deletions catch2/examples/catch2-console/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
idf_component_register(SRCS "test_main.cpp"
"test_cases.cpp"
INCLUDE_DIRS "."
WHOLE_ARCHIVE
PRIV_REQUIRES console)
4 changes: 4 additions & 0 deletions catch2/examples/catch2-console/main/idf_component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dependencies:
espressif/catch2:
version: "*"
override_path: "../../../"
11 changes: 11 additions & 0 deletions catch2/examples/catch2-console/main/test_cases.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <catch2/catch_test_macros.hpp>

TEST_CASE("Test case 1")
{
REQUIRE(1 == 1);
}
45 changes: 45 additions & 0 deletions catch2/examples/catch2-console/main/test_main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include "esp_console.h"
#include "cmd_catch2.h"

#if SOC_USB_SERIAL_JTAG_SUPPORTED
#if !CONFIG_ESP_CONSOLE_SECONDARY_NONE
#warning "A secondary serial console is not useful when using the console component. Please disable it in menuconfig."
#endif
#endif

extern "C" void app_main(void)
{
esp_console_repl_t *repl = NULL;
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
repl_config.prompt = "catch2>";
repl_config.task_stack_size = 10000;

/* Register commands */
esp_console_register_help_command();
register_catch2("test");

#if defined(CONFIG_ESP_CONSOLE_UART_DEFAULT) || defined(CONFIG_ESP_CONSOLE_UART_CUSTOM)
esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl));

#elif defined(CONFIG_ESP_CONSOLE_USB_CDC)
esp_console_dev_usb_cdc_config_t hw_config = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_cdc(&hw_config, &repl_config, &repl));

#elif defined(CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG)
esp_console_dev_usb_serial_jtag_config_t hw_config = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&hw_config, &repl_config, &repl));

#else
#error Unsupported console type
#endif

ESP_ERROR_CHECK(esp_console_start_repl(repl));
}
1 change: 1 addition & 0 deletions catch2/examples/catch2-console/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONFIG_COMPILER_CXX_EXCEPTIONS=y
7 changes: 7 additions & 0 deletions catch2/examples/catch2-test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.16)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)

project(catch2-test)

36 changes: 36 additions & 0 deletions catch2/examples/catch2-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Catch2 example

This example should help you get started with Catch2 test framework.

## Using the example

To run the example on an ESP32, build and flash the project as usual:

```bash
idf.py set-target esp32
idf.py build flash monitor
```

The example can also be used on Linux host:
```bash
idf.py --preview set-target linux
idf.py build monitor
```

## Example structure

- [main/idf_component.yml](main/idf_component.yml) adds a dependency on `espressif/catch2` component.
- [main/CMakeLists.txt](main/CMakeLists.txt) specifies the source files and registers the `main` component with `WHOLE_ARCHIVE` option enabled.
- [main/test_main.cpp](main/test_main.cpp) implements the application entry point which calls the test runner.
- [main/test_cases.cpp](main/test_cases.cpp) implements one trivial test case.
- [sdkconfig.defaults](sdkconfig.defaults) sets the options required to run the example: enables C++ exceptions and increases the size of the `main` task stack.

## Expected output

```
Randomness seeded to: 3499211612
===============================================================================
All tests passed (1 assertion in 1 test case)
Test passed.
```
4 changes: 4 additions & 0 deletions catch2/examples/catch2-test/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
idf_component_register(SRCS "test_main.cpp"
"test_cases.cpp"
INCLUDE_DIRS "."
WHOLE_ARCHIVE)
4 changes: 4 additions & 0 deletions catch2/examples/catch2-test/main/idf_component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dependencies:
espressif/catch2:
version: "*"
override_path: "../../../"
Loading

0 comments on commit 29d3182

Please sign in to comment.