mirror of
https://github.com/esp8266/Arduino.git
synced 2025-07-30 16:24:09 +03:00
Device side test library and test runner
This commit is contained in:
committed by
Ivan Grokhotkov
parent
33723a9b52
commit
ab7af89002
57
tests/device/libraries/BSTest/src/BSArduino.h
Normal file
57
tests/device/libraries/BSTest/src/BSArduino.h
Normal file
@ -0,0 +1,57 @@
|
||||
#ifndef BS_ARDUINO_H
|
||||
#define BS_ARDUINO_H
|
||||
|
||||
#include <Arduino.h>
|
||||
namespace bs
|
||||
{
|
||||
class ArduinoIOHelper
|
||||
{
|
||||
public:
|
||||
ArduinoIOHelper(Stream& stream) : m_stream(stream)
|
||||
{
|
||||
}
|
||||
|
||||
size_t printf(const char *format, ...)
|
||||
{
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
char temp[128];
|
||||
char* buffer = temp;
|
||||
size_t len = vsnprintf(temp, sizeof(temp), format, arg);
|
||||
va_end(arg);
|
||||
if (len > sizeof(temp) - 1) {
|
||||
buffer = new char[len + 1];
|
||||
if (!buffer) {
|
||||
return 0;
|
||||
}
|
||||
va_start(arg, format);
|
||||
ets_vsnprintf(buffer, len + 1, format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
len = m_stream.write((const uint8_t*) buffer, len);
|
||||
if (buffer != temp) {
|
||||
delete[] buffer;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
bool read_int(int& result)
|
||||
{
|
||||
// TODO: fix this for 0 value
|
||||
result = m_stream.parseInt();
|
||||
return result != 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
Stream& m_stream;
|
||||
};
|
||||
|
||||
typedef ArduinoIOHelper IOHelper;
|
||||
|
||||
inline void fatal() {
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
} // namespace bs
|
||||
|
||||
#endif //BS_ARDUINO_H
|
55
tests/device/libraries/BSTest/src/BSProtocol.h
Normal file
55
tests/device/libraries/BSTest/src/BSProtocol.h
Normal file
@ -0,0 +1,55 @@
|
||||
#ifndef BS_PROTOCOL_H
|
||||
#define BS_PROTOCOL_H
|
||||
|
||||
#define BS_LINE_PREFIX ">>>>>bs_test_"
|
||||
|
||||
namespace bs
|
||||
{
|
||||
namespace protocol
|
||||
{
|
||||
template<typename IO>
|
||||
void output_test_start(IO& io, const char* file, size_t line, const char* name, const char* desc)
|
||||
{
|
||||
io.printf(BS_LINE_PREFIX "start file=\"%s\" line=%d name=\"%s\" desc=\"%s\"\n", file, line, name, desc);
|
||||
}
|
||||
|
||||
template<typename IO>
|
||||
void output_check_failure(IO& io, size_t line)
|
||||
{
|
||||
io.printf(BS_LINE_PREFIX "check_failure line=%d\n", line);
|
||||
}
|
||||
|
||||
template<typename IO>
|
||||
void output_test_end(IO& io, bool success, size_t checks, size_t failed_checks, size_t line=0)
|
||||
{
|
||||
io.printf(BS_LINE_PREFIX "end line=%d result=%d checks=%d failed_checks=%d\n", line, success, checks, failed_checks);
|
||||
}
|
||||
|
||||
template<typename IO>
|
||||
void output_menu_begin(IO& io)
|
||||
{
|
||||
io.printf(BS_LINE_PREFIX "menu_begin\n");
|
||||
}
|
||||
|
||||
template<typename IO>
|
||||
void output_menu_item(IO& io, int index, const char* name, const char* desc)
|
||||
{
|
||||
io.printf(BS_LINE_PREFIX "item id=%d name=\"%s\" desc=\"%s\"\n", index, name, desc);
|
||||
}
|
||||
|
||||
template<typename IO>
|
||||
void output_menu_end(IO& io)
|
||||
{
|
||||
io.printf(BS_LINE_PREFIX "menu_end\n");
|
||||
}
|
||||
|
||||
template<typename IO>
|
||||
bool input_menu_choice(IO& io, int& result)
|
||||
{
|
||||
return io.read_int(result);
|
||||
}
|
||||
|
||||
} // ::protocol
|
||||
} // ::bs
|
||||
|
||||
#endif //BS_PROTOCOL_H
|
39
tests/device/libraries/BSTest/src/BSStdio.h
Normal file
39
tests/device/libraries/BSTest/src/BSStdio.h
Normal file
@ -0,0 +1,39 @@
|
||||
#ifndef BS_STDIO_H
|
||||
#define BS_STDIO_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <exception>
|
||||
|
||||
namespace bs
|
||||
{
|
||||
class StdIOHelper
|
||||
{
|
||||
public:
|
||||
StdIOHelper()
|
||||
{
|
||||
}
|
||||
|
||||
size_t printf(const char *format, ...)
|
||||
{
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
size_t result = vprintf(format, arg);
|
||||
va_end(arg);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool read_int(int& result)
|
||||
{
|
||||
return scanf("%d", &result) == 1;
|
||||
}
|
||||
};
|
||||
|
||||
typedef StdIOHelper IOHelper;
|
||||
|
||||
inline void fatal() {
|
||||
throw std::runtime_error("fatal error");
|
||||
}
|
||||
|
||||
} // namespace bs
|
||||
|
||||
#endif //BS_STDIO_H
|
217
tests/device/libraries/BSTest/src/BSTest.h
Normal file
217
tests/device/libraries/BSTest/src/BSTest.h
Normal file
@ -0,0 +1,217 @@
|
||||
#ifndef BSTEST_H
|
||||
#define BSTEST_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include "BSProtocol.h"
|
||||
|
||||
#if defined(ARDUINO)
|
||||
#include "BSArduino.h"
|
||||
#else
|
||||
#include "BSStdio.h"
|
||||
#endif
|
||||
|
||||
namespace bs
|
||||
{
|
||||
typedef void(*test_case_func_t)();
|
||||
|
||||
class TestCase
|
||||
{
|
||||
public:
|
||||
TestCase(TestCase* prev, test_case_func_t func, const char* file, size_t line, const char* name, const char* desc)
|
||||
: m_func(func), m_file(file), m_line(line), m_name(name), m_desc(desc)
|
||||
{
|
||||
if (prev) {
|
||||
prev->m_next = this;
|
||||
}
|
||||
}
|
||||
|
||||
void run() const
|
||||
{
|
||||
(*m_func)();
|
||||
}
|
||||
|
||||
TestCase* next() const
|
||||
{
|
||||
return m_next;
|
||||
}
|
||||
|
||||
const char* file() const
|
||||
{
|
||||
return m_file;
|
||||
}
|
||||
|
||||
size_t line() const
|
||||
{
|
||||
return m_line;
|
||||
}
|
||||
|
||||
const char* name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
const char* desc() const
|
||||
{
|
||||
return (m_desc)?m_desc:"";
|
||||
}
|
||||
|
||||
protected:
|
||||
TestCase* m_next = nullptr;
|
||||
test_case_func_t m_func;
|
||||
const char* m_file;
|
||||
size_t m_line;
|
||||
const char* m_name;
|
||||
const char* m_desc;
|
||||
};
|
||||
|
||||
struct Registry {
|
||||
void add(test_case_func_t func, const char* file, size_t line, const char* name, const char* desc)
|
||||
{
|
||||
TestCase* tc = new TestCase(m_last, func, file, line, name, desc);
|
||||
if (!m_first) {
|
||||
m_first = tc;
|
||||
}
|
||||
m_last = tc;
|
||||
}
|
||||
TestCase* m_first = nullptr;
|
||||
TestCase* m_last = nullptr;
|
||||
};
|
||||
|
||||
struct Env {
|
||||
std::function<void(void)> m_check_pass;
|
||||
std::function<void(size_t)> m_check_fail;
|
||||
std::function<void(size_t)> m_fail;
|
||||
Registry m_registry;
|
||||
};
|
||||
|
||||
extern Env g_env;
|
||||
|
||||
template<typename IO>
|
||||
class Runner
|
||||
{
|
||||
typedef Runner<IO> Tself;
|
||||
public:
|
||||
Runner(IO& io) : m_io(io)
|
||||
{
|
||||
g_env.m_check_pass = std::bind(&Tself::check_pass, this);
|
||||
g_env.m_check_fail = std::bind(&Tself::check_fail, this, std::placeholders::_1);
|
||||
g_env.m_fail = std::bind(&Tself::fail, this, std::placeholders::_1);
|
||||
}
|
||||
|
||||
~Runner()
|
||||
{
|
||||
g_env.m_check_pass = 0;
|
||||
g_env.m_check_fail = 0;
|
||||
g_env.m_fail = 0;
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
do {
|
||||
} while(do_menu());
|
||||
}
|
||||
|
||||
void check_pass()
|
||||
{
|
||||
++m_check_pass_count;
|
||||
}
|
||||
|
||||
void check_fail(size_t line)
|
||||
{
|
||||
++m_check_fail_count;
|
||||
protocol::output_check_failure(m_io, line);
|
||||
}
|
||||
|
||||
void fail(size_t line)
|
||||
{
|
||||
protocol::output_test_end(m_io, false, m_check_pass_count + m_check_fail_count, m_check_fail_count, line);
|
||||
bs::fatal();
|
||||
}
|
||||
|
||||
protected:
|
||||
bool do_menu()
|
||||
{
|
||||
protocol::output_menu_begin(m_io);
|
||||
int id = 1;
|
||||
for (TestCase* tc = g_env.m_registry.m_first; tc; tc = tc->next(), ++id) {
|
||||
protocol::output_menu_item(m_io, id, tc->name(), tc->desc());
|
||||
}
|
||||
protocol::output_menu_end(m_io);
|
||||
while(true) {
|
||||
int id;
|
||||
if (!protocol::input_menu_choice(m_io, id)) {
|
||||
continue;
|
||||
}
|
||||
if (id < 0) {
|
||||
return true;
|
||||
}
|
||||
TestCase* tc = g_env.m_registry.m_first;
|
||||
for (int i = 0; i != id - 1 && tc; ++i, tc = tc->next());
|
||||
if (!tc) {
|
||||
bs::fatal();
|
||||
}
|
||||
m_check_pass_count = 0;
|
||||
m_check_fail_count = 0;
|
||||
protocol::output_test_start(m_io, tc->file(), tc->line(), tc->name(), tc->desc());
|
||||
tc->run();
|
||||
bool success = m_check_fail_count == 0;
|
||||
protocol::output_test_end(m_io, success, m_check_pass_count + m_check_fail_count, m_check_fail_count);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
IO& m_io;
|
||||
size_t m_check_pass_count;
|
||||
size_t m_check_fail_count;
|
||||
};
|
||||
|
||||
class AutoReg
|
||||
{
|
||||
public:
|
||||
AutoReg(test_case_func_t func, const char* file, size_t line, const char* name, const char* desc = nullptr)
|
||||
{
|
||||
g_env.m_registry.add(func, file, line, name, desc);
|
||||
}
|
||||
};
|
||||
|
||||
inline void check(bool condition, size_t line)
|
||||
{
|
||||
if (!condition) {
|
||||
g_env.m_check_fail(line);
|
||||
} else {
|
||||
g_env.m_check_pass();
|
||||
}
|
||||
}
|
||||
|
||||
inline void require(bool condition, size_t line)
|
||||
{
|
||||
if (!condition) {
|
||||
g_env.m_check_fail(line);
|
||||
g_env.m_fail(line);
|
||||
} else {
|
||||
g_env.m_check_pass();
|
||||
}
|
||||
}
|
||||
|
||||
} // ::bs
|
||||
|
||||
#define BS_NAME_LINE2( name, line ) name##line
|
||||
#define BS_NAME_LINE( name, line ) BS_NAME_LINE2( name, line )
|
||||
#define BS_UNIQUE_NAME( name ) BS_NAME_LINE( name, __LINE__ )
|
||||
|
||||
#define TEST_CASE( ... ) \
|
||||
static void BS_UNIQUE_NAME( TEST_FUNC__ )(); \
|
||||
namespace{ bs::AutoReg BS_UNIQUE_NAME( test_autoreg__ )( &BS_UNIQUE_NAME( TEST_FUNC__ ), __FILE__, __LINE__, __VA_ARGS__ ); }\
|
||||
static void BS_UNIQUE_NAME( TEST_FUNC__ )()
|
||||
|
||||
#define CHECK(condition) bs::check((condition), __LINE__)
|
||||
#define REQUIRE(condition) bs::require((condition), __LINE__)
|
||||
#define FAIL() bs::g_env.m_fail(__LINE__)
|
||||
|
||||
#define BS_ENV_DECLARE() namespace bs { Env g_env; }
|
||||
#define BS_RUN(...) do { bs::IOHelper helper = bs::IOHelper(__VA_ARGS__); bs::Runner<bs::IOHelper> runner(helper); runner.run(); } while(0);
|
||||
#endif //BSTEST_H
|
Reference in New Issue
Block a user