[CI] Unit test (#1373)
* [CI] Add basic unit testing * [CI] Add gitignore * [CI] Install libfmt --------- Co-authored-by: jgromes <jan.gromes>
This commit is contained in:
parent
a63ca70558
commit
cbb8d419d1
8 changed files with 486 additions and 0 deletions
27
.github/workflows/unit-test.yml
vendored
Normal file
27
.github/workflows/unit-test.yml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
name: "Unit test"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
unit-test:
|
||||
name: Build and run unit test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libboost-all-dev libfmt-dev
|
||||
|
||||
- name: Run unit test
|
||||
run: |
|
||||
cd extras/test/unit
|
||||
./test.sh
|
1
extras/test/unit/.gitignore
vendored
Normal file
1
extras/test/unit/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
build/
|
29
extras/test/unit/CMakeLists.txt
Normal file
29
extras/test/unit/CMakeLists.txt
Normal file
|
@ -0,0 +1,29 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
project(radiolib-unittest)
|
||||
|
||||
# add RadioLib sources
|
||||
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../../../../RadioLib" "${CMAKE_CURRENT_BINARY_DIR}/RadioLib")
|
||||
|
||||
# add test sources
|
||||
file(GLOB_RECURSE TEST_SOURCES
|
||||
"tests/main.cpp"
|
||||
"tests/TestModule.cpp"
|
||||
)
|
||||
|
||||
# create the executable
|
||||
add_executable(${PROJECT_NAME} ${TEST_SOURCES})
|
||||
|
||||
# include directories
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC include)
|
||||
|
||||
# link RadioLib
|
||||
target_link_libraries(${PROJECT_NAME} RadioLib fmt)
|
||||
|
||||
# set target properties and options
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra)
|
||||
|
||||
# set RadioLib debug
|
||||
#target_compile_definitions(RadioLib PUBLIC RADIOLIB_DEBUG_BASIC RADIOLIB_DEBUG_SPI)
|
||||
#target_compile_definitions(RadioLib PUBLIC RADIOLIB_DEBUG_PORT=stdout)
|
71
extras/test/unit/include/HardwareEmulation.hpp
Normal file
71
extras/test/unit/include/HardwareEmulation.hpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
#ifndef HARDWARE_EMULATION_HPP
|
||||
#define HARDWARE_EMULATION_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// value that is returned by the emualted radio class when performing SPI transfer to it
|
||||
#define EMULATED_RADIO_SPI_RETURN (0xFF)
|
||||
|
||||
// pin indexes
|
||||
#define EMULATED_RADIO_NSS_PIN (1)
|
||||
#define EMULATED_RADIO_IRQ_PIN (2)
|
||||
#define EMULATED_RADIO_RST_PIN (3)
|
||||
#define EMULATED_RADIO_GPIO_PIN (4)
|
||||
|
||||
enum PinFunction_t {
|
||||
PIN_UNASSIGNED = 0,
|
||||
PIN_CS,
|
||||
PIN_IRQ,
|
||||
PIN_RST,
|
||||
PIN_GPIO,
|
||||
};
|
||||
|
||||
// structure for emulating GPIO pins
|
||||
struct EmulatedPin_t {
|
||||
uint32_t mode;
|
||||
uint32_t value;
|
||||
bool event;
|
||||
PinFunction_t func;
|
||||
};
|
||||
|
||||
// structure for emulating SPI registers
|
||||
struct EmulatedRegister_t {
|
||||
uint8_t value;
|
||||
uint8_t readOnlyBitFlags;
|
||||
bool bufferAccess;
|
||||
};
|
||||
|
||||
// base class for emulated radio modules (SX126x etc.)
|
||||
class EmulatedRadio {
|
||||
public:
|
||||
void connect(EmulatedPin_t* csPin, EmulatedPin_t* irqPin, EmulatedPin_t* rstPin, EmulatedPin_t* gpioPin) {
|
||||
this->cs = csPin;
|
||||
this->cs->func = PIN_CS;
|
||||
this->irq = irqPin;
|
||||
this->irq->func = PIN_IRQ;
|
||||
this->rst = rstPin;
|
||||
this->rst->func = PIN_RST;
|
||||
this->gpio = gpioPin;
|
||||
this->gpio->func = PIN_GPIO;
|
||||
}
|
||||
|
||||
virtual uint8_t HandleSPI(uint8_t b) {
|
||||
(void)b;
|
||||
// handle the SPI input and generate output here
|
||||
return(EMULATED_RADIO_SPI_RETURN);
|
||||
}
|
||||
|
||||
virtual void HandleGPIO() {
|
||||
// handle discrete GPIO signals here (e.g. reset state machine on NSS falling edge)
|
||||
}
|
||||
|
||||
protected:
|
||||
// pointers to emulated GPIO pins
|
||||
// this is done via pointers so that the same GPIO entity is shared, like with a real hardware
|
||||
EmulatedPin_t* cs;
|
||||
EmulatedPin_t* irq;
|
||||
EmulatedPin_t* rst;
|
||||
EmulatedPin_t* gpio;
|
||||
};
|
||||
|
||||
#endif
|
237
extras/test/unit/include/TestHal.hpp
Normal file
237
extras/test/unit/include/TestHal.hpp
Normal file
|
@ -0,0 +1,237 @@
|
|||
#ifndef TEST_HAL_HPP
|
||||
#define TEST_HAL_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <RadioLib.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#if defined(TEST_HAL_LOG)
|
||||
#define HAL_LOG(...) BOOST_TEST_MESSAGE(__VA_ARGS__)
|
||||
#else
|
||||
#define HAL_LOG(...) {}
|
||||
#endif
|
||||
|
||||
#include "HardwareEmulation.hpp"
|
||||
|
||||
#define TEST_HAL_INPUT (0)
|
||||
#define TEST_HAL_OUTPUT (1)
|
||||
#define TEST_HAL_LOW (0)
|
||||
#define TEST_HAL_HIGH (1)
|
||||
#define TEST_HAL_RISING (0)
|
||||
#define TEST_HAL_FALLING (1)
|
||||
|
||||
// number of emulated GPIO pins
|
||||
#define TEST_HAL_NUM_GPIO_PINS (32)
|
||||
|
||||
#define TEST_HAL_SPI_LOG_LENGTH (512)
|
||||
|
||||
class TestHal : public RadioLibHal {
|
||||
public:
|
||||
TestHal() : RadioLibHal(TEST_HAL_INPUT, TEST_HAL_OUTPUT, TEST_HAL_LOW, TEST_HAL_HIGH, TEST_HAL_RISING, TEST_HAL_FALLING) { }
|
||||
|
||||
void init() override {
|
||||
HAL_LOG("TestHal::init()");
|
||||
|
||||
// save program start timestamp
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// init emulated GPIO
|
||||
for(int i = 0; i < TEST_HAL_NUM_GPIO_PINS; i++) {
|
||||
this->gpio[i].mode = 0;
|
||||
this->gpio[i].value = 0;
|
||||
this->gpio[i].event = false;
|
||||
this->gpio[i].func = PIN_UNASSIGNED;
|
||||
}
|
||||
}
|
||||
|
||||
void term() override {
|
||||
HAL_LOG("TestHal::term()");
|
||||
}
|
||||
|
||||
void pinMode(uint32_t pin, uint32_t mode) override {
|
||||
HAL_LOG("TestHal::pinMode(pin=" << pin << ", mode=" << mode << " [" << ((mode == TEST_HAL_INPUT) ? "INPUT" : "OUTPUT") << "])");
|
||||
|
||||
// check the range
|
||||
BOOST_ASSERT_MSG(pin < TEST_HAL_NUM_GPIO_PINS, "Pin number out of range");
|
||||
|
||||
// check known modes
|
||||
BOOST_ASSERT_MSG(((mode == TEST_HAL_INPUT) || (mode == TEST_HAL_OUTPUT)), "Invalid pin mode");
|
||||
|
||||
// set mode
|
||||
this->gpio[pin].mode = mode;
|
||||
}
|
||||
|
||||
void digitalWrite(uint32_t pin, uint32_t value) override {
|
||||
HAL_LOG("TestHal::digitalWrite(pin=" << pin << ", value=" << value << " [" << ((value == TEST_HAL_LOW) ? "LOW" : "HIGH") << "])");
|
||||
|
||||
// check the range
|
||||
BOOST_ASSERT_MSG(pin < TEST_HAL_NUM_GPIO_PINS, "Pin number out of range");
|
||||
|
||||
// check it is output
|
||||
BOOST_ASSERT_MSG(this->gpio[pin].mode == TEST_HAL_OUTPUT, "GPIO is not output!");
|
||||
|
||||
// check known values
|
||||
BOOST_ASSERT_MSG(((value == TEST_HAL_LOW) || (value == TEST_HAL_HIGH)), "Invalid output value");
|
||||
|
||||
// set value
|
||||
this->gpio[pin].value = value;
|
||||
this->gpio[pin].event = true;
|
||||
if(radio) {
|
||||
this->radio->HandleGPIO();
|
||||
}
|
||||
this->gpio[pin].event = false;
|
||||
}
|
||||
|
||||
uint32_t digitalRead(uint32_t pin) override {
|
||||
HAL_LOG("TestHal::digitalRead(pin=" << pin << ")");
|
||||
|
||||
// check the range
|
||||
BOOST_ASSERT_MSG(pin < TEST_HAL_NUM_GPIO_PINS, "Pin number out of range");
|
||||
|
||||
// check it is input
|
||||
BOOST_ASSERT_MSG(this->gpio[pin].mode == TEST_HAL_INPUT, "GPIO is not input");
|
||||
|
||||
// read the value
|
||||
uint32_t value = this->gpio[pin].value;
|
||||
HAL_LOG("TestHal::digitalRead(pin=" << pin << ")=" << value << " [" << ((value == TEST_HAL_LOW) ? "LOW" : "HIGH") << "]");
|
||||
return(value);
|
||||
}
|
||||
|
||||
void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override {
|
||||
HAL_LOG("TestHal::attachInterrupt(interruptNum=" << interruptNum << ", interruptCb=" << interruptCb << ", mode=" << mode << ")");
|
||||
}
|
||||
|
||||
void detachInterrupt(uint32_t interruptNum) override {
|
||||
HAL_LOG("TestHal::detachInterrupt(interruptNum=" << interruptNum << ")");
|
||||
}
|
||||
|
||||
void delay(unsigned long ms) override {
|
||||
HAL_LOG("TestHal::delay(ms=" << ms << ")");
|
||||
const auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// sleep_for is sufficient for ms-precision sleep
|
||||
std::this_thread::sleep_for(std::chrono::duration<unsigned long, std::milli>(ms));
|
||||
|
||||
// measure and print
|
||||
const auto end = std::chrono::high_resolution_clock::now();
|
||||
const std::chrono::duration<double, std::milli> elapsed = end - start;
|
||||
HAL_LOG("TestHal::delay(ms=" << ms << ")=" << elapsed.count() << "ms");
|
||||
}
|
||||
|
||||
void delayMicroseconds(unsigned long us) override {
|
||||
HAL_LOG("TestHal::delayMicroseconds(us=" << us << ")");
|
||||
const auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// busy wait is needed for microseconds precision
|
||||
const auto len = std::chrono::microseconds(us);
|
||||
while(std::chrono::high_resolution_clock::now() - start < len);
|
||||
|
||||
// measure and print
|
||||
const auto end = std::chrono::high_resolution_clock::now();
|
||||
const std::chrono::duration<double, std::micro> elapsed = end - start;
|
||||
HAL_LOG("TestHal::delayMicroseconds(us=" << us << ")=" << elapsed.count() << "us");
|
||||
}
|
||||
|
||||
void yield() override {
|
||||
HAL_LOG("TestHal::yield()");
|
||||
}
|
||||
|
||||
unsigned long millis() override {
|
||||
HAL_LOG("TestHal::millis()");
|
||||
std::chrono::time_point now = std::chrono::high_resolution_clock::now();
|
||||
auto res = std::chrono::duration_cast<std::chrono::milliseconds>(now - this->start);
|
||||
HAL_LOG("TestHal::millis()=" << res.count());
|
||||
return(res.count());
|
||||
}
|
||||
|
||||
unsigned long micros() override {
|
||||
HAL_LOG("TestHal::micros()");
|
||||
std::chrono::time_point now = std::chrono::high_resolution_clock::now();
|
||||
auto res = std::chrono::duration_cast<std::chrono::microseconds>(now - this->start);
|
||||
HAL_LOG("TestHal::micros()=" << res.count());
|
||||
return(res.count());
|
||||
}
|
||||
|
||||
long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override {
|
||||
HAL_LOG("TestHal::pulseIn(pin=" << pin << ", state=" << state << ", timeout=" << timeout << ")");
|
||||
return(0);
|
||||
}
|
||||
|
||||
void spiBegin() {
|
||||
HAL_LOG("TestHal::spiBegin()");
|
||||
}
|
||||
|
||||
void spiBeginTransaction() {
|
||||
HAL_LOG("TestHal::spiBeginTransaction()");
|
||||
|
||||
// wipe history log
|
||||
memset(this->spiLog, 0x00, TEST_HAL_SPI_LOG_LENGTH);
|
||||
this->spiLogPtr = this->spiLog;
|
||||
}
|
||||
|
||||
void spiTransfer(uint8_t* out, size_t len, uint8_t* in) {
|
||||
HAL_LOG("TestHal::spiTransfer(len=" << len << ")");
|
||||
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
// append to log
|
||||
(*this->spiLogPtr++) = out[i];
|
||||
|
||||
// process the SPI byte
|
||||
in[i] = this->radio->HandleSPI(out[i]);
|
||||
|
||||
// outpu debug
|
||||
HAL_LOG(fmt::format("out={:#02x}, in={:#02x}", out[i], in[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void spiEndTransaction() {
|
||||
HAL_LOG("TestHal::spiEndTransaction()");
|
||||
}
|
||||
|
||||
void spiEnd() {
|
||||
HAL_LOG("TestHal::spiEnd()");
|
||||
}
|
||||
|
||||
void tone(uint32_t pin, unsigned int frequency, unsigned long duration = 0) {
|
||||
HAL_LOG("TestHal::tone(pin=" << pin << ", frequency=" << frequency << ", duration=" << duration << ")");
|
||||
}
|
||||
|
||||
void noTone(uint32_t pin) {
|
||||
HAL_LOG("TestHal::noTone(pin=" << pin << ")");
|
||||
}
|
||||
|
||||
// method to compare buffer to the internal SPI log, for verifying SPI transactions
|
||||
int spiLogMemcmp(const void* in, size_t n) {
|
||||
return(memcmp(this->spiLog, in, n));
|
||||
}
|
||||
|
||||
// method that "connects" the emualted radio hardware to this HAL
|
||||
void connectRadio(EmulatedRadio* r) {
|
||||
this->radio = r;
|
||||
this->radio->connect(&this->gpio[EMULATED_RADIO_NSS_PIN],
|
||||
&this->gpio[EMULATED_RADIO_IRQ_PIN],
|
||||
&this->gpio[EMULATED_RADIO_RST_PIN],
|
||||
&this->gpio[EMULATED_RADIO_GPIO_PIN]);
|
||||
}
|
||||
|
||||
private:
|
||||
// array of emulated GPIO pins
|
||||
EmulatedPin_t gpio[TEST_HAL_NUM_GPIO_PINS];
|
||||
|
||||
// start time point
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> start;
|
||||
|
||||
// emulated radio hardware
|
||||
EmulatedRadio* radio;
|
||||
|
||||
// SPI history log
|
||||
uint8_t spiLog[TEST_HAL_SPI_LOG_LENGTH];
|
||||
uint8_t* spiLogPtr;
|
||||
};
|
||||
|
||||
#endif
|
13
extras/test/unit/test.sh
Executable file
13
extras/test/unit/test.sh
Executable file
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# build the test binary
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake -G "CodeBlocks - Unix Makefiles" ..
|
||||
make -j4
|
||||
|
||||
# run it
|
||||
cd ..
|
||||
./build/radiolib-unittest --log_level=message
|
103
extras/test/unit/tests/TestModule.cpp
Normal file
103
extras/test/unit/tests/TestModule.cpp
Normal file
|
@ -0,0 +1,103 @@
|
|||
// boost test header
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
// mock HAL
|
||||
#include "TestHal.hpp"
|
||||
|
||||
// testing fixture
|
||||
struct ModuleFixture {
|
||||
TestHal* hal = nullptr;
|
||||
Module* mod = nullptr;
|
||||
EmulatedRadio* radioHardware = nullptr;
|
||||
|
||||
ModuleFixture() {
|
||||
BOOST_TEST_MESSAGE("--- Module fixture setup ---");
|
||||
hal = new TestHal();
|
||||
radioHardware = new EmulatedRadio();
|
||||
hal->connectRadio(radioHardware);
|
||||
|
||||
mod = new Module(hal, EMULATED_RADIO_NSS_PIN, EMULATED_RADIO_IRQ_PIN, EMULATED_RADIO_RST_PIN, EMULATED_RADIO_GPIO_PIN);
|
||||
mod->init();
|
||||
}
|
||||
|
||||
~ModuleFixture() {
|
||||
BOOST_TEST_MESSAGE("--- Module fixture teardown ---");
|
||||
mod->term();
|
||||
delete[] mod;
|
||||
delete[] hal;
|
||||
}
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(suite_Module, ModuleFixture)
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(Module_SPIgetRegValue_reg, ModuleFixture)
|
||||
{
|
||||
BOOST_TEST_MESSAGE("--- Test Module::SPIgetRegValue register access ---");
|
||||
int16_t ret;
|
||||
|
||||
// basic register read with default config
|
||||
const uint8_t address = 0x12;
|
||||
const uint8_t spiTxn[] = { address, 0x00 };
|
||||
ret = mod->SPIgetRegValue(address);
|
||||
|
||||
// check return code, value and history log
|
||||
BOOST_TEST(ret >= RADIOLIB_ERR_NONE);
|
||||
BOOST_TEST(ret == EMULATED_RADIO_SPI_RETURN);
|
||||
BOOST_TEST(hal->spiLogMemcmp(spiTxn, sizeof(spiTxn)) == 0);
|
||||
|
||||
// register read masking test
|
||||
const uint8_t msb = 5;
|
||||
const uint8_t lsb = 1;
|
||||
ret = mod->SPIgetRegValue(address, msb, lsb);
|
||||
BOOST_TEST(ret == 0x3E);
|
||||
|
||||
// invalid mask tests (swapped MSB and LSB, out of range bit masks)
|
||||
ret = mod->SPIgetRegValue(address, lsb, msb);
|
||||
BOOST_TEST(ret == RADIOLIB_ERR_INVALID_BIT_RANGE);
|
||||
ret = mod->SPIgetRegValue(address, 10, lsb);
|
||||
BOOST_TEST(ret == RADIOLIB_ERR_INVALID_BIT_RANGE);
|
||||
ret = mod->SPIgetRegValue(address, msb, 10);
|
||||
BOOST_TEST(ret == RADIOLIB_ERR_INVALID_BIT_RANGE);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(Module_SPIgetRegValue_stream, ModuleFixture)
|
||||
{
|
||||
BOOST_TEST_MESSAGE("--- Test Module::SPIgetRegValue stream access ---");
|
||||
int16_t ret;
|
||||
|
||||
// change settings to stream type
|
||||
mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR] = Module::BITS_16;
|
||||
mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_8;
|
||||
mod->spiConfig.statusPos = 1;
|
||||
mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] = RADIOLIB_SX126X_CMD_READ_REGISTER;
|
||||
mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] = RADIOLIB_SX126X_CMD_WRITE_REGISTER;
|
||||
mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_NOP] = RADIOLIB_SX126X_CMD_NOP;
|
||||
mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_STATUS] = RADIOLIB_SX126X_CMD_GET_STATUS;
|
||||
mod->spiConfig.stream = true;
|
||||
|
||||
// basic register read
|
||||
const uint8_t address = 0x12;
|
||||
const uint8_t spiTxn[] = { RADIOLIB_SX126X_CMD_READ_REGISTER, 0x00, address, 0x00, 0x00 };
|
||||
ret = mod->SPIgetRegValue(address);
|
||||
|
||||
// check return code, value and history log
|
||||
BOOST_TEST(ret >= RADIOLIB_ERR_NONE);
|
||||
BOOST_TEST(ret == EMULATED_RADIO_SPI_RETURN);
|
||||
BOOST_TEST(hal->spiLogMemcmp(spiTxn, sizeof(spiTxn)) == 0);
|
||||
|
||||
// register read masking test
|
||||
const uint8_t msb = 5;
|
||||
const uint8_t lsb = 1;
|
||||
ret = mod->SPIgetRegValue(address, msb, lsb);
|
||||
BOOST_TEST(ret == 0x3E);
|
||||
|
||||
// invalid mask tests (swapped MSB and LSB, out of range bit masks)
|
||||
ret = mod->SPIgetRegValue(address, lsb, msb);
|
||||
BOOST_TEST(ret == RADIOLIB_ERR_INVALID_BIT_RANGE);
|
||||
ret = mod->SPIgetRegValue(address, 10, lsb);
|
||||
BOOST_TEST(ret == RADIOLIB_ERR_INVALID_BIT_RANGE);
|
||||
ret = mod->SPIgetRegValue(address, msb, 10);
|
||||
BOOST_TEST(ret == RADIOLIB_ERR_INVALID_BIT_RANGE);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
5
extras/test/unit/tests/main.cpp
Normal file
5
extras/test/unit/tests/main.cpp
Normal file
|
@ -0,0 +1,5 @@
|
|||
#define BOOST_TEST_MODULE "RadioLib Unit test"
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
// intentionally left blank, boost.test creates its own entrypoint
|
||||
|
Loading…
Add table
Reference in a new issue