Skip to content

Commit 1b352d6

Browse files
bors[bot]jenswet
andauthored
Merge #17066
17066: sys/irq: Add C++ wrapper using RAII r=maribu a=jenswet ### Contribution description This adds a C++ wrapper around the `irq.h` API. The wrapper uses RAII to accomplish a convenient and bug resistent use. A little background: I'm currently writing my master thesis on using C++ for embedded development, at the working group that `@maribu` is part of. For that I will try to add better C++ support to several parts of RIOT and then do some benchmarking and metrics to compare it with the C implementation. For example, I also plan to add a wrapper around i2c, a std::cout drop-in replacement and probably some more about networks or threads. ### Testing procedure I've added a unit test to verify that the IRQ wrapper calls the original `irq` functions as expected. As C++ and wrapper testing isn't done much so far in this project, I've added two additional headers to ease testing: 1. #17076 - fake functions framework, already merged 2. As there is no framework for C++ unit tests yet, I've added something for this too. Unfortunately the existing frameworks like GoogleTest, CppUTest or CppUnit don't easily compile for embedded or are difficult to integrate in to the RIOT build process. That's why I wrote some (simple) helper functions and macros inspired by the above frameworks. That allows to create C++ tests based on a fixture class with set up and tear down methods. It also allows some simple assertions and is easily extendable for other use cases. It wraps some of the fff functionality too. Both of this is obviously not required for the initial reason of this PR. But I'd like to provide unit tests for the features that I suggest to introduce where possible. So I'd appreciate some feedback on that too. If you'd prefer a PR without or different tests please let me know. You can run the test `irq_cpp` locally or on the CI to test the implementation. Please feel free to give feedback or suggest improvements! Co-authored-by: Jens Wetterich <[email protected]>
2 parents b8b1a60 + a9c5987 commit 1b352d6

File tree

11 files changed

+653
-0
lines changed

11 files changed

+653
-0
lines changed

pkg/fff/Makefile.include

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ INCLUDES += -I$(PKGDIRBASE)/fff
33
# There's nothing to build in this package, it's used as a header only library.
44
# So it's declared as a pseudo-module
55
PSEUDOMODULES += fff
6+
7+
# Tests don't need pedantic. Pedantic throws errors in variadic macros when compiling for C++
8+
CXXEXFLAGS += -Wno-pedantic

sys/include/cppunit.hpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (C) 2021 Jens Wetterich <[email protected]>
3+
*
4+
* This file is subject to the terms and conditions of the GNU Lesser
5+
* General Public License v2.1. See the file LICENSE in the top level
6+
* directory for more details.
7+
*/
8+
/**
9+
* @ingroup sys
10+
* @defgroup unittests_cpp C++ Unittests
11+
* @brief RIOT unit tests for C++
12+
* @details The C++ unit test framework syntax is loosely based on
13+
* GoogleTest, but offers far less functionality.
14+
* For mocking the package @ref pkg_fff can be used.
15+
* @{
16+
* @file
17+
* @brief RIOT unit tests for C++
18+
* @details The C++ unit test framework syntax is loosely based on GoogleTest,
19+
* but offers far less functionality.
20+
* For mocking the package @ref pkg_fff can be used.
21+
* @author Jens Wetterich <[email protected]>
22+
*
23+
*/
24+
#ifndef CPPUNIT_H
25+
#define CPPUNIT_H
26+
#if __cplusplus >= 201103L || defined(DOXYGEN)
27+
#include "cppunit/cppunit_base.hpp"
28+
#include "cppunit/cppunit_expect.hpp"
29+
#include "cppunit/cppunit_fff.hpp"
30+
#else
31+
#error This library needs C++11 and newer
32+
#endif
33+
#endif
34+
/** @} */
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
* Copyright (C) 2021 Jens Wetterich <[email protected]>
3+
*
4+
* This file is subject to the terms and conditions of the GNU Lesser
5+
* General Public License v2.1. See the file LICENSE in the top level
6+
* directory for more details.
7+
*/
8+
/**
9+
* @addtogroup unittests_cpp
10+
* @{
11+
* @file
12+
* @brief RIOT unit tests for C++ base classes and macros
13+
* @author Jens Wetterich <[email protected]>
14+
*
15+
*/
16+
#ifndef CPPUNIT_BASE_H
17+
#define CPPUNIT_BASE_H
18+
#if __cplusplus >= 201103L || defined(DOXYGEN)
19+
#include <array>
20+
#include <cstdio>
21+
#include <cstring>
22+
#include <type_traits>
23+
#ifndef CPPUNIT_SUITE_CNT
24+
/**
25+
* @brief Maximum amount of tests in one test suite
26+
*/
27+
#define CPPUNIT_SUITE_CNT (10)
28+
#endif
29+
/**
30+
* @brief RIOT C++ namespace
31+
*/
32+
namespace riot {
33+
/**
34+
* @brief namespace for cpp unit tests
35+
*/
36+
namespace testing {
37+
/**
38+
* @brief Test base class
39+
* @details Should not be instantiated directly.
40+
* @sa #TEST(suite, name) macro
41+
*/
42+
class test {
43+
private:
44+
bool suc = true; ///< indicates success of the test after running
45+
public:
46+
/**
47+
* @brief Run the test
48+
* @details Should not be called directly, only via macros
49+
* @sa #RUN_SUITE(name)
50+
* @return whether the test completed without errors
51+
*/
52+
virtual bool run() = 0;
53+
/**
54+
* @brief Indicate failure during test run
55+
* @details Should be called by assertions macros
56+
* @sa #EXPECT_EQ(expected, actual, msg)
57+
* @sa #EXPECT_STREQ(expected, actual, msg)
58+
* @sa #EXPECT_FFF_CALL_COUNT(name, count)
59+
* @sa #EXPECT_FFF_CALL_PARAMS(mock, ...)
60+
*/
61+
void fail() {
62+
suc = false;
63+
}
64+
/**
65+
* @brief Check whether the test completed successfully
66+
* @return whether the test completed without errors
67+
*/
68+
bool success() const {
69+
return suc;
70+
}
71+
};
72+
/**
73+
* @brief Test suite base class
74+
* @details Should not be instantiated directly.
75+
* To customize a test suite with custom set_up and tear down methods,
76+
* inherit from this class and override set_up() and/or tear_down().
77+
* They will before / after every test.
78+
* @sa #TEST_SUITE(name) macro
79+
* @sa #TEST_SUITE_F(suite, name) macro
80+
* @sa test_suite::set_up()
81+
* @sa test_suite::tear_down()
82+
*/
83+
class test_suite {
84+
protected:
85+
bool suc = true; ///< Indicates success of all tests after running the suite
86+
std::array<test*, CPPUNIT_SUITE_CNT> tests{}; ///< array of tests to run
87+
public:
88+
/**
89+
* @brief method to run before each test
90+
*/
91+
virtual void set_up() {
92+
}
93+
/**
94+
* @brief method to run after each test
95+
*/
96+
virtual void tear_down() {
97+
}
98+
/**
99+
* @brief get the name of the test suite
100+
* @return name string
101+
*/
102+
virtual const char* get_name() const {
103+
return "";
104+
};
105+
/**
106+
* @brief Run all tests in the suite
107+
*/
108+
virtual void run() {
109+
printf("----\nStarting Test suite %s\n", get_name());
110+
for (auto test : tests) {
111+
if (test) {
112+
suc = test->run() && suc;
113+
}
114+
}
115+
printf("Suite %s completed: %s\n----\n", get_name(), suc ? "SUCCESS" : "FAILURE");
116+
}
117+
/**
118+
* @brief Run all tests in the suite
119+
*/
120+
void addTest(test* test) {
121+
for (int i = 0; i < CPPUNIT_SUITE_CNT; i++) {
122+
if (!tests[i]) {
123+
tests[i] = test;
124+
break;
125+
}
126+
}
127+
}
128+
};
129+
}// namespace testing
130+
}// namespace riot
131+
/**
132+
* @brief Run the test suite \a name
133+
* @hideinitializer
134+
* @param[in] name Name of the suite
135+
*/
136+
#define RUN_SUITE(name) test_suite_##name.run();
137+
138+
/**
139+
* @brief Instantiate a test suite with custom test fixture
140+
* @hideinitializer
141+
* @param[in] suite Name of the custom test fixture class
142+
* @param[in] name Instantiation name of the suite
143+
*/
144+
#define TEST_SUITE_F(suite, name) \
145+
static_assert(sizeof(#suite) > 1, "test fixture class must not be empty"); \
146+
static_assert(sizeof(#name) > 1, "test_suite name must not be empty"); \
147+
class test_suite_##name : public suite { \
148+
const char* get_name() const override { \
149+
return #name; \
150+
}; \
151+
}; \
152+
test_suite_##name test_suite_##name;
153+
154+
/**
155+
* @brief Instantiate a test suite
156+
* @hideinitializer
157+
* @param[in] name Instantiation name of the suite
158+
*/
159+
#define TEST_SUITE(name) TEST_SUITE_F(::riot::testing::test_suite, name)
160+
161+
/**
162+
* @brief Begin the definition of a test
163+
* @details Insert the test body after the macro
164+
* @hideinitializer
165+
* @param[in] suite Name of the suite to add the test to
166+
* @param[in] name Instantiation name of the test
167+
*/
168+
#define TEST(suite, name) \
169+
class Test_##name : public ::riot::testing::test { \
170+
private: \
171+
void test_body(); \
172+
\
173+
public: \
174+
Test_##name() { \
175+
test_suite_##suite.addTest(this); \
176+
} \
177+
bool run() { \
178+
test_suite_##suite.set_up(); \
179+
printf("Running test " #name "...\n"); \
180+
test_body(); \
181+
printf("Test " #name ": %s\n", success() ? "SUCCESS" : "FAILURE"); \
182+
test_suite_##suite.tear_down(); \
183+
return success(); \
184+
}; \
185+
}; \
186+
void Test_##name::test_body()
187+
#else
188+
#error This library needs C++11 and newer
189+
#endif
190+
#endif
191+
/** @} */
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright (C) 2021 Jens Wetterich <[email protected]>
3+
*
4+
* This file is subject to the terms and conditions of the GNU Lesser
5+
* General Public License v2.1. See the file LICENSE in the top level
6+
* directory for more details.
7+
*/
8+
/**
9+
* @addtogroup unittests_cpp
10+
* @{
11+
* @file
12+
* @brief RIOT unit tests for C++ assertion macros
13+
* @author Jens Wetterich <[email protected]>
14+
*
15+
*/
16+
#ifndef CPPUNIT_EXPECT_H
17+
#define CPPUNIT_EXPECT_H
18+
#if __cplusplus >= 201103L || defined(DOXYGEN)
19+
/**
20+
* @brief Expect equality of the \a actual and \a expected value
21+
* @hideinitializer
22+
* @param[in] expected Expected value
23+
* @param[in] actual Actual value
24+
* @param[in] msg Message to print in case of failure
25+
*/
26+
#define EXPECT_EQ(expected, actual, msg) \
27+
static_assert(std::is_integral<decltype(expected)>::value, \
28+
"EXPECT_EQ requires an integral type "); \
29+
if ((actual) != (expected)) { \
30+
fail(); \
31+
if (std::is_same<decltype(expected), bool>::value) { \
32+
printf("Expected: %s, actual: %s\n" msg "\n", (expected) ? "true" : "false", \
33+
(actual) ? "true" : "false"); \
34+
} \
35+
else if (std::is_unsigned<decltype(expected)>::value) { \
36+
printf("Expected: %u, actual: %u\n" msg "\n", static_cast<unsigned>(expected), \
37+
static_cast<unsigned>(actual)); \
38+
} \
39+
else { \
40+
printf("Expected: %d, actual: %d\n" msg "\n", static_cast<int>(expected), \
41+
static_cast<int>(actual)); \
42+
} \
43+
}
44+
/**
45+
* @brief Expect string equality of the \a actual and \a expected value
46+
* @details Interprets both values as const char* string
47+
* @hideinitializer
48+
* @param[in] expected Expected value
49+
* @param[in] actual Actual value
50+
* @param[in] msg Message to print in case of failure
51+
*/
52+
#define EXPECT_STREQ(expected, actual, msg) \
53+
auto expected_str = static_cast<const char*>(expected); \
54+
auto actual_str = static_cast<const char*>(actual); \
55+
if (strcmp(expected_str, actual_str) != 0) { \
56+
fail(); \
57+
printf(msg " not equal! Expected: %s, actual: %s\n", expected_str, actual_str); \
58+
}
59+
#else
60+
#error This library needs C++11 and newer
61+
#endif
62+
#endif
63+
/** @} */

0 commit comments

Comments
 (0)