1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-07-21 21:22:31 +03:00

Merge pull request #1977 from esp8266/device_tests

First batch of automated tests on the ESP: test library and test runner
This commit is contained in:
Ivan Grokhotkov
2016-05-05 21:56:05 -05:00
27 changed files with 1224 additions and 207 deletions

View File

@ -36,7 +36,7 @@ script:
- install_libraries - install_libraries
- echo -e "travis_fold:end:sketch_test_env_prepare" - echo -e "travis_fold:end:sketch_test_env_prepare"
- echo -e "travis_fold:start:sketch_test" - echo -e "travis_fold:start:sketch_test"
- build_sketches $HOME/arduino_ide $TRAVIS_BUILD_DIR "-l $HOME/Arduino/libraries" - build_sketches $HOME/arduino_ide $TRAVIS_BUILD_DIR/libraries "-l $HOME/Arduino/libraries"
- echo -e "travis_fold:end:sketch_test" - echo -e "travis_fold:end:sketch_test"
- echo -e "travis_fold:start:size_report" - echo -e "travis_fold:start:size_report"
- cat size.log - cat size.log

1
tests/.gitignore vendored
View File

@ -1,3 +1,2 @@
hardware hardware
tmp tmp
.env

View File

@ -1,155 +0,0 @@
#include <ESP8266WiFi.h>
#include "FS.h"
void fail(const char* msg) {
Serial.println(msg);
while (true) {
yield();
}
}
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
WiFi.mode(WIFI_OFF);
Serial.println("\n\nFS test\n");
{
if (!SPIFFS.format()) {
fail("format failed");
}
Dir root = SPIFFS.openDir("/");
int count = 0;
while (root.next()) {
++count;
}
if (count > 0) {
fail("some files left after format");
}
}
if (!SPIFFS.begin()) {
fail("SPIFFS init failed");
}
String text = "write test";
{
File out = SPIFFS.open("/tmp.txt", "w");
if (!out) {
fail("failed to open tmp.txt for writing");
}
out.print(text);
}
{
File in = SPIFFS.open("/tmp.txt", "r");
if (!in) {
fail("failed to open tmp.txt for reading");
}
Serial.printf("size=%d\r\n", in.size());
if (in.size() != text.length()) {
fail("invalid size of tmp.txt");
}
Serial.print("Reading data: ");
in.setTimeout(0);
String result = in.readString();
Serial.println(result);
if (result != text) {
fail("invalid data in tmp.txt");
}
}
{
for (int i = 0; i < 10; ++i) {
String name = "seq_";
name += i;
name += ".txt";
File out = SPIFFS.open(name, "w");
if (!out) {
fail("can't open seq_ file");
}
out.println(i);
}
}
{
Dir root = SPIFFS.openDir("/");
while (root.next()) {
String fileName = root.fileName();
File f = root.openFile("r");
Serial.printf("%s: %d\r\n", fileName.c_str(), f.size());
}
}
{
Dir root = SPIFFS.openDir("/");
while (root.next()) {
String fileName = root.fileName();
Serial.print("deleting ");
Serial.println(fileName);
if (!SPIFFS.remove(fileName)) {
fail("remove failed");
}
}
}
{
File tmp = SPIFFS.open("/tmp1.txt", "w");
tmp.println("rename test");
}
{
if (!SPIFFS.rename("/tmp1.txt", "/tmp2.txt")) {
fail("rename failed");
}
File tmp2 = SPIFFS.open("/tmp2.txt", "r");
if (!tmp2) {
fail("open tmp2 failed");
}
}
{
FSInfo info;
if (!SPIFFS.info(info)) {
fail("info failed");
}
Serial.printf("Total: %u\nUsed: %u\nBlock: %u\nPage: %u\nMax open files: %u\nMax path len: %u\n",
info.totalBytes,
info.usedBytes,
info.blockSize,
info.pageSize,
info.maxOpenFiles,
info.maxPathLength
);
}
{
if (!SPIFFS.format()) {
fail("format failed");
}
Dir root = SPIFFS.openDir("/");
int count = 0;
while (root.next()) {
++count;
}
if (count > 0) {
fail("some files left after format");
}
}
{
File tmp = SPIFFS.open("/tmp.txt", "w");
}
{
File tmp = SPIFFS.open("/tmp.txt", "w");
if (!tmp) {
fail("failed to re-open empty file");
}
}
Serial.println("success");
}
void loop() {
}

View File

@ -1,35 +0,0 @@
#include <ESP8266WiFi.h>
#include <time.h>
const char* ssid = "..........";
const char* password = "..........";
int timezone = 3;
int dst = 0;
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("\nConnecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov");
Serial.println("\nWaiting for time");
while (!time(nullptr)) {
Serial.print(".");
delay(1000);
}
Serial.println("");
}
void loop() {
time_t now = time(nullptr);
Serial.println(ctime(&now));
delay(1000);
}

2
tests/device/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.build
.hardware

98
tests/device/Makefile Normal file
View File

@ -0,0 +1,98 @@
SHELL := /bin/bash
V ?= 0
TEST_LIST ?= $(wildcard test_*/*.ino)
ESP8266_CORE_PATH ?= ../..
BUILD_DIR ?= $(PWD)/.build
HARDWARE_DIR ?= $(PWD)/.hardware
ESPTOOL ?= $(ESP8266_CORE_PATH)/tools/esptool/esptool
UPLOAD_PORT ?= $(shell ls /dev/tty* | grep -m 1 -i USB)
UPLOAD_BAUD ?= 921600
UPLOAD_BOARD ?= nodemcu
BS_DIR ?= libraries/BSTest
DEBUG_LEVEL ?= DebugLevel=None____
FQBN ?= esp8266com:esp8266:generic:CpuFrequency=80,FlashFreq=40,FlashMode=DIO,UploadSpeed=115200,FlashSize=4M1M,ResetMethod=none,Debug=Serial,$(DEBUG_LEVEL)
BUILD_TOOL = $(ARDUINO_IDE_PATH)/arduino-builder
TEST_CONFIG = libraries/test_config/test_config.h
ifeq ("$(UPLOAD_PORT)","")
$(error "Failed to detect upload port, please export UPLOAD_PORT manually")
endif
ifeq ("$(ARDUINO_IDE_PATH)","")
$(error "Please export ARDUINO_IDE_PATH")
endif
ifneq ("$(V)","1")
SILENT = @
else
BUILDER_DEBUG_FLAG = -verbose
RUNNER_DEBUG_FLAG = -d
UPLOAD_VERBOSE_FLAG = -v
endif
all: count tests
count:
@echo Running $(words $(TEST_LIST)) tests
tests: $(BUILD_DIR) $(HARDWARE_DIR) virtualenv $(TEST_CONFIG) $(TEST_LIST)
$(TEST_LIST): LOCAL_BUILD_DIR=$(BUILD_DIR)/$(notdir $@)
$(TEST_LIST):
$(SILENT)mkdir -p $(LOCAL_BUILD_DIR)
ifneq ("$(NO_BUILD)","1")
@echo Compiling $(notdir $@)
$(SILENT)$(BUILD_TOOL) -compile -logger=human \
-libraries "$(PWD)/libraries" \
-core-api-version="10608" \
-warnings=none \
$(BUILDER_DEBUG_FLAG) \
-build-path $(LOCAL_BUILD_DIR) \
-tools $(ARDUINO_IDE_PATH)/tools-builder \
-hardware $(HARDWARE_DIR)\
-hardware $(ARDUINO_IDE_PATH)/hardware \
-fqbn=$(FQBN) \
$@
endif
ifneq ("$(NO_UPLOAD)","1")
$(SILENT)$(ESPTOOL) $(UPLOAD_VERBOSE_FLAG) \
-cp $(UPLOAD_PORT) \
-cb $(UPLOAD_BAUD) \
-cd $(UPLOAD_BOARD) \
-cf $(LOCAL_BUILD_DIR)/$(notdir $@).bin
endif
ifneq ("$(NO_RUN)","1")
@echo Running tests
$(SILENT)$(ESPTOOL) $(UPLOAD_VERBOSE_FLAG) -cp $(UPLOAD_PORT) -cd $(UPLOAD_BOARD) -cr
@source $(BS_DIR)/virtualenv/bin/activate && \
python $(BS_DIR)/runner.py \
$(RUNNER_DEBUG_FLAG) \
-p $(UPLOAD_PORT) \
-n $(basename $(notdir $@)) \
-o $(LOCAL_BUILD_DIR)/test_result.xml
endif
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
$(HARDWARE_DIR):
mkdir -p $(HARDWARE_DIR)/esp8266com
cd $(HARDWARE_DIR)/esp8266com && ln -s $(realpath $(ESP8266_CORE_PATH)) esp8266
virtualenv:
make -C $(BS_DIR) virtualenv
clean:
rm -rf $(BUILD_DIR)
rm -rf $(HARDWARE_DIR)
$(TEST_CONFIG):
@echo "****** "
@echo "****** libraries/test_config/test_config.h does not exist"
@echo "****** Create one from libraries/test_config/test_config.h.template"
@echo "****** "
false
.PHONY: tests all count venv $(BUILD_DIR) $(TEST_LIST)

View File

@ -0,0 +1,3 @@
test/test
virtualenv

View File

@ -0,0 +1,22 @@
PYTHON_ENV_DIR=virtualenv
TEST_EXECUTABLE=test/test
all: test
install: $(PYTHON_ENV_DIR)
clean:
rm -rf $(PYTHON_ENV_DIR)
rm -rf $(TEST_EXECUTABLE)
$(PYTHON_ENV_DIR):
virtualenv --no-site-packages $(PYTHON_ENV_DIR)
source $(PYTHON_ENV_DIR)/bin/activate && pip install -r requirements.txt
test: $(TEST_EXECUTABLE) $(PYTHON_ENV_DIR)
source $(PYTHON_ENV_DIR)/bin/activate && python runner.py -e $(TEST_EXECUTABLE)
$(TEST_EXECUTABLE): test/test.cpp
g++ -std=c++11 -Isrc -o $@ test/test.cpp
.PHONY: test clean install all

View File

@ -0,0 +1,9 @@
name=BSTest
version=0.1
author=Ivan Grokhotkov <ivan@espressif.com>
maintainer=Ivan Grokhotkov <ivan@espressif.com>
sentence=BS Test library
paragraph=
category=Uncategorized
url=
architectures=esp8266

View File

@ -0,0 +1,6 @@
junit-xml==1.6
pexpect==4.0.1
ptyprocess==0.5.1
pyserial==3.0.1
six==1.10.0
wheel==0.24.0

View File

@ -0,0 +1,189 @@
#!/usr/bin/env python
from __future__ import print_function
import pexpect
from pexpect import EOF, TIMEOUT, fdpexpect
import sys
import os
import time
import argparse
import serial
import subprocess
from urlparse import urlparse
from junit_xml import TestSuite, TestCase
try:
from cStringIO import StringIO
except:
from StringIO import StringIO
debug = False
def debug_print(*args, **kwargs):
if not debug:
return
print(file=sys.stderr, *args, **kwargs)
class BSTestRunner(object):
SUCCESS = 0
FAIL = 1
TIMEOUT = 2
CRASH = 3
def __init__(self, spawn_obj, name):
self.sp = spawn_obj
self.tests = []
self.reset_timeout = 2
self.name = name
def get_test_list(self):
self.sp.sendline('-1')
timeout = 10
while timeout > 0:
res = self.sp.expect(['>>>>>bs_test_menu_begin', EOF])
if res == 0:
break
timeout-=1
time.sleep(0.1)
debug_print('got begin')
self.tests = []
while True:
res = self.sp.expect(['>>>>>bs_test_item id\=(\d+) name\="([^\"]*?)" desc="([^"]*?)"',
'>>>>>bs_test_menu_end',
EOF])
if res == 0:
m = self.sp.match
t = {'id': m.group(1), 'name': m.group(2), 'desc': m.group(3)}
self.tests.append(t)
debug_print('added test', t)
elif res == 1:
break
elif res == 2:
time.sleep(0.1)
debug_print('got {} tests'.format(len(self.tests)))
def run_tests(self):
test_cases = []
for test in self.tests:
desc = test['desc']
name = test['name']
index = test['id']
test_case = TestCase(name, self.name)
if '[.]' in desc:
print('skipping test "{}"'.format(name))
test_case.add_skipped_info(message="Skipped test marked with [.]")
else:
test_output = StringIO()
self.sp.logfile = test_output
t_start = time.time()
result = self.run_test(index)
t_stop = time.time()
self.sp.logfile = None
test_case.elapsed_sec = t_stop - t_start
debug_print('test output was:')
debug_print(test_output.getvalue())
if result == BSTestRunner.SUCCESS:
test_case.stdout = test_output.getvalue()
print('test "{}" passed'.format(name))
else:
print('test "{}" failed'.format(name))
test_case.add_failure_info('Test failed', output=test_output.getvalue())
test_output.close()
test_cases += [test_case];
return TestSuite(self.name, test_cases)
def run_test(self, index):
self.sp.sendline('{}'.format(index))
timeout = 10
while timeout > 0:
res = self.sp.expect(['>>>>>bs_test_start', EOF])
if res == 0:
break
time.sleep(0.1)
timeout -= 0.1
if timeout <= 0:
raise 'test begin timeout'
while timeout > 0:
res = self.sp.expect(['>>>>>bs_test_check_failure line=(\d+)',
'>>>>>bs_test_end line=(\d+) result=(\d+) checks=(\d+) failed_checks=(\d+)',
TIMEOUT,
EOF,
'Exception',
'ets Jan 8 2013',
'wdt reset'])
if res == 0:
continue
elif res == 1:
test_result = self.sp.match.group(2)
if test_result == '1':
return BSTestRunner.SUCCESS
else:
if self.sp.match.group(1) != '0':
time.sleep(1.0)
self.sp.expect([TIMEOUT,
'wdt reset',
'Exception',
'Panic',
'Abort',
'Soft WDT',
EOF], timeout=self.reset_timeout)
return BSTestRunner.FAIL
elif res == 2 or res == 3:
time.sleep(0.1)
timeout -= 0.1
continue
elif res > 3:
return BSTestRunner.CRASH
if timeout <= 0:
return BSTestRunner.TIMEOUT
ser = None
def spawn_port(port_name, baudrate=115200):
global ser
ser = serial.serial_for_url(port_name, baudrate=baudrate)
return fdpexpect.fdspawn(ser, 'wb', timeout=0)
def spawn_exec(name):
return pexpect.spawn(name, timeout=0)
def run_tests(spawn, name):
tw = BSTestRunner(spawn, name)
tw.get_test_list()
return tw.run_tests()
def parse_args():
parser = argparse.ArgumentParser(description='BS test runner')
parser.add_argument('-d', '--debug', help='Send test output to stderr', action='store_true')
parser.add_argument('-p', '--port', help='Talk to the test over serial')
parser.add_argument('-e', '--executable', help='Talk to the test executable')
parser.add_argument('-n', '--name', help='Test run name')
parser.add_argument('-o', '--output', help='Output JUnit format test report')
return parser.parse_args()
def main():
args = parse_args()
spawn_func = None
spawn_arg = None
if args.port is not None:
spawn_func = spawn_port
spawn_arg = args.port
elif args.executable is not None:
spawn_func = spawn_exec
spawn_arg = args.executable
name = args.name or ""
global debug
if args.debug:
debug = True
if spawn_func is None:
debug_print("Please specify port or executable", file=sys.stderr)
return 1
with spawn_func(spawn_arg) as sp:
ts = run_tests(sp, name)
if args.output:
with open(args.output, "w") as f:
TestSuite.to_file(f, [ts])
return 0
if __name__ == '__main__':
sys.exit(main())

View 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

View 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

View 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

View 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

View File

@ -0,0 +1,51 @@
#include "BSTest.h"
#include <stdio.h>
BS_ENV_DECLARE();
int main()
{
while(true) {
try{
BS_RUN();
return 0;
}catch(...) {
printf("Exception\n\n");
}
}
return 1;
}
TEST_CASE("this test runs successfully", "[bluesmoke]")
{
CHECK(1 + 1 == 2);
REQUIRE(2 * 2 == 4);
}
TEST_CASE("another test which fails", "[bluesmoke][fail]")
{
CHECK(true);
CHECK(false);
CHECK(true);
CHECK(false);
}
TEST_CASE("another test which fails and crashes", "[bluesmoke][fail]")
{
CHECK(true);
REQUIRE(false);
}
TEST_CASE("third test which should be skipped", "[.]")
{
FAIL();
}
TEST_CASE("this test also runs successfully", "[bluesmoke]")
{
}

View File

@ -0,0 +1 @@
test_config.h

View File

@ -0,0 +1,9 @@
name=TestConfig
version=0.0
author=
maintainer=
sentence=
paragraph=
category=Uncategorized
url=
architectures=esp8266

View File

@ -0,0 +1,8 @@
#pragma once
#define STA_SSID "test_wifi"
#define STA_PASS "test_wifi_pass"
#define AP_SSID "test_wifi_ap"
#define AP_PASS "test_wifi_ap_pass"

View File

@ -0,0 +1,136 @@
#include <ESP8266WiFi.h>
#include "FS.h"
#include <BSTest.h>
BS_ENV_DECLARE();
void setup()
{
Serial.begin(115200);
BS_RUN(Serial);
}
TEST_CASE("read-write test","[fs]")
{
REQUIRE(SPIFFS.begin());
String text = "write test";
{
File out = SPIFFS.open("/tmp.txt", "w");
REQUIRE(out);
out.print(text);
}
{
File in = SPIFFS.open("/tmp.txt", "r");
REQUIRE(in);
CHECK(in.size() == text.length());
in.setTimeout(0);
String result = in.readString();
CHECK(result == text);
}
}
TEST_CASE("A bunch of files show up in openDir, and can be removed", "[fs]")
{
REQUIRE(SPIFFS.begin());
const int n = 10;
int found[n] = {0};
for (int i = 0; i < n; ++i) {
String name = "seq_";
name += i;
name += ".txt";
File out = SPIFFS.open(name, "w");
REQUIRE(out);
out.println(i);
}
{
Dir root = SPIFFS.openDir("/");
while (root.next()) {
String fileName = root.fileName();
CHECK(fileName.indexOf("seq_") == 0);
int i = fileName.substring(4).toInt();
CHECK(i >= 0 && i < n);
found[i]++;
}
for (auto f : found) {
CHECK(f == 1);
}
}
{
Dir root = SPIFFS.openDir("/");
while (root.next()) {
String fileName = root.fileName();
CHECK(SPIFFS.remove(fileName));
}
}
}
TEST_CASE("files can be renamed", "[fs]")
{
REQUIRE(SPIFFS.begin());
{
File tmp = SPIFFS.open("/tmp1.txt", "w");
tmp.println("rename test");
}
{
CHECK(SPIFFS.rename("/tmp1.txt", "/tmp2.txt"));
File tmp2 = SPIFFS.open("/tmp2.txt", "r");
CHECK(tmp2);
}
}
TEST_CASE("FS::info works")
{
REQUIRE(SPIFFS.begin());
FSInfo info;
CHECK(SPIFFS.info(info));
Serial.printf("Total: %u\nUsed: %u\nBlock: %u\nPage: %u\nMax open files: %u\nMax path len: %u\n",
info.totalBytes,
info.usedBytes,
info.blockSize,
info.pageSize,
info.maxOpenFiles,
info.maxPathLength
);
}
TEST_CASE("FS is empty after format","[fs]")
{
REQUIRE(SPIFFS.begin());
REQUIRE(SPIFFS.format());
Dir root = SPIFFS.openDir("/");
int count = 0;
while (root.next()) {
++count;
}
CHECK(count == 0);
}
TEST_CASE("Can reopen empty file","[fs]")
{
REQUIRE(SPIFFS.begin());
{
File tmp = SPIFFS.open("/tmp.txt", "w");
}
{
File tmp = SPIFFS.open("/tmp.txt", "w");
CHECK(tmp);
}
}
void loop()
{
}

View File

@ -0,0 +1,38 @@
#include <BSTest.h>
#include <StreamString.h>
BS_ENV_DECLARE();
void setup()
{
Serial.begin(115200);
BS_RUN(Serial);
}
TEST_CASE("Print::printf works for any reasonable output length", "[Print]")
{
auto test_printf = [](size_t size) {
StreamString str;
auto buf = new char[size + 1];
for (int i = 0; i < size; ++i) {
buf[i] = 'a';
}
buf[size] = 0;
str.printf("%s%8d", buf, 56789102);
delete[] buf;
CHECK(str.length() == size + 8);
CHECK(str.substring(size) == "56789102");
};
auto before = ESP.getFreeHeap();
test_printf(1);
test_printf(10);
test_printf(100);
test_printf(1000);
test_printf(10000);
auto after = ESP.getFreeHeap();
CHECK(before == after);
}
void loop() {}

View File

@ -0,0 +1,140 @@
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <BSTest.h>
#include <test_config.h>
#include <pgmspace.h>
BS_ENV_DECLARE();
void setup()
{
Serial.begin(115200);
WiFi.persistent(false);
WiFi.begin(STA_SSID, STA_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
BS_RUN(Serial);
}
// json doesn't allow newlines within a string
auto test_payload = (const __FlashStringHelper *) "\"\
0000000 53 48 45 4c 4c 20 3a 3d 20 2f 62 69 6e 2f 62 61\\n\
0000010 73 68 0a 56 20 3f 3d 20 30 0a 54 45 53 54 5f 4c\\n\
0000020 49 53 54 20 3f 3d 20 24 28 77 69 6c 64 63 61 72\\n\
0000030 64 20 74 65 73 74 5f 2a 2f 2a 2e 69 6e 6f 29 0a\\n\
0000040 45 53 50 38 32 36 36 5f 43 4f 52 45 5f 50 41 54\\n\
0000050 48 20 3f 3d 20 2e 2e 2f 2e 2e 0a 42 55 49 4c 44\\n\
0000060 5f 44 49 52 20 3f 3d 20 24 28 50 57 44 29 2f 2e\\n\
0000070 62 75 69 6c 64 0a 48 41 52 44 57 41 52 45 5f 44\\n\
0000080 49 52 20 3f 3d 20 24 28 50 57 44 29 2f 2e 68 61\\n\
0000090 72 64 77 61 72 65 0a 45 53 50 54 4f 4f 4c 20 3f\\n\
00000a0 3d 20 24 28 45 53 50 38 32 36 36 5f 43 4f 52 45\\n\
123\"";
auto bin_template_1 = (const __FlashStringHelper *) R"({
"status": 200,
"statusText": "OK",
"httpVersion": "HTTP/1.1",
"headers": [],
"cookies": [],
"content": {
"mimeType": "text/plain",
"text":
)";
auto bin_template_2 = (const __FlashStringHelper *) R"(
}
})";
const char *mockbin_fingerprint = "F0 C1 7B 6A 89 4F AA 67 86 15 4A 17 6C 25 52 8E 2F F6 0F 34";
String createBin(bool https)
{
String payload;
payload.reserve(
strlen_P((PGM_P) bin_template_1) +
strlen_P((PGM_P) test_payload) +
strlen_P((PGM_P) bin_template_2) + 1);
payload += bin_template_1;
payload += test_payload;
payload += bin_template_2;
HTTPClient http;
if (https) {
http.begin("https://mockbin.org/bin/create", mockbin_fingerprint);
}
else {
http.begin("http://mockbin.org/bin/create");
}
const char* location = "Location";
http.collectHeaders(&location, 1);
http.addHeader("Content-Type", "application/json");
auto result = http.POST(payload);
Serial.println(result);
Serial.println("----");
Serial.println(http.getString());
Serial.println("----");
REQUIRE(result == 201);
String url = "http://mockbin.org" + http.header(location);
http.end();
return url;
}
TEST_CASE("HTTP GET request", "[HTTPClient]")
{
const int repeatCount = 10;
String url = createBin(false);
int heapBefore = ESP.getFreeHeap();
for (int i = 0; i < repeatCount; ++i) {
HTTPClient http;
http.begin(url);
auto httpCode = http.GET();
REQUIRE(httpCode == HTTP_CODE_OK);
String payload = http.getString();
payload.replace("\n", "\\n");
String quotedPayload;
quotedPayload.reserve(payload.length() + 3);
quotedPayload += "\"";
quotedPayload += payload;
quotedPayload += "\"";
Serial.println("----payload:");
Serial.println(quotedPayload);
Serial.println("----");
Serial.println("----test_payload:");
Serial.println(test_payload);
Serial.println("----");
CHECK(quotedPayload == test_payload);
http.end();
delay(100);
}
int heapAfter = ESP.getFreeHeap();
CHECK(heapBefore - heapAfter <= 8);
}
TEST_CASE("HTTPS GET request", "[HTTPClient]")
{
const int repeatCount = 10;
String url = createBin(true);
int heapBefore = ESP.getFreeHeap();
for (int i = 0; i < repeatCount; ++i) {
HTTPClient http;
http.begin(url, mockbin_fingerprint);
auto httpCode = http.GET();
REQUIRE(httpCode == HTTP_CODE_OK);
String payload = http.getString();
CHECK(payload == test_payload);
http.end();
delay(100);
}
int heapAfter = ESP.getFreeHeap();
CHECK(heapBefore - heapAfter <= 8);
}
void loop()
{
}

View File

@ -0,0 +1,38 @@
#include <BSTest.h>
BS_ENV_DECLARE();
ADC_MODE(ADC_VCC);
RF_MODE(RF_CAL);
static int rf_pre_init_flag = 0;
RF_PRE_INIT()
{
rf_pre_init_flag = 42;
}
static unsigned setup_micros;
void setup()
{
setup_micros = micros();
Serial.begin(115200);
BS_RUN(Serial);
}
TEST_CASE("ADC_MODE override works", "[core]")
{
auto vcc = ESP.getVcc();
Serial.printf("VCC: %d\r\n", vcc);
Serial.printf("A0: %d\r\n", analogRead(A0));
CHECK(vcc > 3000 && vcc < 3600);
}
TEST_CASE("RF_PRE_INIT override works", "[core]")
{
CHECK(rf_pre_init_flag == 42);
}
void loop()
{
}

View File

@ -0,0 +1,47 @@
#include <BSTest.h>
#include <test_config.h>
BS_ENV_DECLARE();
void setup()
{
Serial.begin(115200);
BS_RUN(Serial);
}
TEST_CASE("this test runs successfully", "[bs]")
{
CHECK(1 + 1 == 2);
REQUIRE(2 * 2 == 4);
}
TEST_CASE("another test which fails", "[bs][fail]")
{
CHECK(true);
CHECK(false);
CHECK(true);
CHECK(false);
}
TEST_CASE("another test which fails and crashes", "[bs][fail]")
{
CHECK(true);
REQUIRE(false);
}
TEST_CASE("third test which should be skipped", "[.]")
{
FAIL();
}
TEST_CASE("this test also runs successfully", "[bs]")
{
}
void loop()
{
}

View File

@ -0,0 +1,46 @@
#include <ESP8266WiFi.h>
#include <time.h>
#include <ESP8266HTTPClient.h>
#include <BSTest.h>
#include <test_config.h>
BS_ENV_DECLARE();
void setup()
{
Serial.begin(115200);
WiFi.persistent(false);
WiFi.begin(STA_SSID, STA_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
BS_RUN(Serial);
}
TEST_CASE("Can sync time", "[time]")
{
int timezone = 3;
int dst = 0;
configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov");
Serial.println("\nWaiting for time");
unsigned timeout = 5000;
unsigned start = millis();
while (millis() - start < timeout) {
time_t now = time(nullptr);
if (now > (2016 - 1970) * 365 * 24 * 3600) {
return;
}
delay(100);
}
{
time_t now = time(nullptr);
Serial.println(ctime(&now));
}
CHECK(false);
}
void loop()
{
}

View File

@ -1,13 +1,21 @@
// test that we can include umm_malloc.h from sketch (#1652) // test that we can include umm_malloc.h from sketch (#1652)
#include <umm_malloc/umm_malloc.h> #include <umm_malloc/umm_malloc.h>
#include <BSTest.h>
void setup() { BS_ENV_DECLARE();
void setup()
{
Serial.begin(115200); Serial.begin(115200);
delay(1000); BS_RUN(Serial);
}
TEST_CASE("umm_info can be called", "[umm_malloc]")
{
umm_info(NULL, 1); umm_info(NULL, 1);
} }
void loop() { void loop()
{
} }

View File

@ -1,11 +0,0 @@
ADC_MODE(ADC_VCC);
RF_MODE(RF_DISABLED);
RF_PRE_INIT()
{
}
void setup() {
}
void loop() {
}