Public Access
Add native unit tests for the Nano firmware
- New native environment: pio test -e native - test/mocks: fake Arduino core, EEPROM and recording BROSE9323 driver - test/test_nano: 25 tests covering every command, bitmaps, bad input, flip time storage and the periodic full redraw - Board environments ignore tests so they are never uploaded Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
80f3f78152
commit
8415dee347
8 files changed
+505
-4
No files matched your search
@@ -16,7 +16,8 @@ See README.md for the overview.
|
|||||||
or `$env:USERPROFILE\.platformio\penv\Scripts\pio.exe run` (PowerShell).
|
or `$env:USERPROFILE\.platformio\penv\Scripts\pio.exe run` (PowerShell).
|
||||||
- If an ESP32 build fails with `No module named 'intelhex'`, run
|
- If an ESP32 build fails with `No module named 'intelhex'`, run
|
||||||
`~/.platformio/penv/Scripts/python.exe -m pip install intelhex`.
|
`~/.platformio/penv/Scripts/python.exe -m pip install intelhex`.
|
||||||
- After any change, build both targets: `pio run -e nano -e esp32`.
|
- After any change, build both targets (`pio run`). After Nano changes, also run
|
||||||
|
`pio test -e native`.
|
||||||
|
|
||||||
## Constraints
|
## Constraints
|
||||||
|
|
||||||
@@ -55,9 +56,13 @@ See README.md for the overview.
|
|||||||
- The Nano only replies `READY` (at boot) and `ERR: ...`. There is no per-frame
|
- The Nano only replies `READY` (at boot) and `ERR: ...`. There is no per-frame
|
||||||
acknowledgement. Whether the Nano's TX is wired to the ESP's RX is unconfirmed, so every
|
acknowledgement. Whether the Nano's TX is wired to the ESP's RX is unconfirmed, so every
|
||||||
wait for a reply needs a timeout.
|
wait for a reply needs a timeout.
|
||||||
- To test the parser without hardware, compile all `src/nano/*.cpp` files with g++ (Strawberry Perl
|
- Unit tests: `pio test -e native` runs `test/test_nano/` on the PC with the fakes in
|
||||||
provides it at `C:\Strawberry\c\bin`) and mock headers for `Arduino.h`, `EEPROM.h` and
|
`test/mocks/` (Arduino, EEPROM, and a `BROSE9323` that records calls). It needs gcc on
|
||||||
`BROSE9323.h`.
|
PATH (Strawberry Perl provides it at `C:\Strawberry\c\bin`). Add a test for every
|
||||||
|
protocol change. The mocks only implement what the Nano code uses, so extend them when
|
||||||
|
new Arduino or driver functions are called.
|
||||||
|
- The board environments have `test_ignore = *`, so test firmware is never uploaded. Plain
|
||||||
|
`pio test` runs nothing; always pass `-e native`.
|
||||||
|
|
||||||
## Git
|
## Git
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ lib/BROSE9323/ shared flipdot driver (used by both targets)
|
|||||||
src/nano/ Arduino Nano firmware
|
src/nano/ Arduino Nano firmware
|
||||||
src/esp32/ ESP32 firmware
|
src/esp32/ ESP32 firmware
|
||||||
secrets.example.h template for WiFi / MQTT credentials
|
secrets.example.h template for WiFi / MQTT credentials
|
||||||
|
test/test_nano/ unit tests for the Nano firmware (run on the PC)
|
||||||
|
test/mocks/ fake Arduino, EEPROM and display driver for the tests
|
||||||
archive/ old, unused sketches kept for reference
|
archive/ old, unused sketches kept for reference
|
||||||
CLAUDE.md project notes for Claude Code
|
CLAUDE.md project notes for Claude Code
|
||||||
```
|
```
|
||||||
@@ -64,8 +66,12 @@ pio run -e nano -t upload # flash the Nano
|
|||||||
pio run -e esp32 -t upload # flash the ESP32 over the air
|
pio run -e esp32 -t upload # flash the ESP32 over the air
|
||||||
pio run -e esp32_usb -t upload # flash the ESP32 over USB
|
pio run -e esp32_usb -t upload # flash the ESP32 over USB
|
||||||
pio device monitor -e nano # serial monitor
|
pio device monitor -e nano # serial monitor
|
||||||
|
pio test -e native # run the Nano unit tests on the PC
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The unit tests need `gcc`/`g++` on your PATH. On Windows you can get it from
|
||||||
|
[MSYS2](https://www.msys2.org/) or Strawberry Perl.
|
||||||
|
|
||||||
In VS Code you can also pick the environment in the PlatformIO status bar.
|
In VS Code you can also pick the environment in the PlatformIO status bar.
|
||||||
|
|
||||||
> **Tip:** The ESP32 → Nano wire goes to the Nano's RX pin, which the Nano's USB chip
|
> **Tip:** The ESP32 → Nano wire goes to the Nano's RX pin, which the Nano's USB chip
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
; nano Arduino Nano that drives the Brose flipdot panels directly
|
; nano Arduino Nano that drives the Brose flipdot panels directly
|
||||||
; esp32 ESP32 "brain" (WiFi, NTP clock, Home Assistant) - OTA upload
|
; esp32 ESP32 "brain" (WiFi, NTP clock, Home Assistant) - OTA upload
|
||||||
; esp32_usb Same ESP32 firmware, flashed over USB (first flash / recovery)
|
; esp32_usb Same ESP32 firmware, flashed over USB (first flash / recovery)
|
||||||
|
; native Unit tests for the Nano firmware, run on the PC (needs gcc on PATH)
|
||||||
;
|
;
|
||||||
; Build all: pio run
|
; Build all: pio run
|
||||||
; Build one: pio run -e nano
|
; Build one: pio run -e nano
|
||||||
; Upload one: pio run -e nano -t upload
|
; Upload one: pio run -e nano -t upload
|
||||||
; Serial monitor: pio device monitor -e nano
|
; Serial monitor: pio device monitor -e nano
|
||||||
|
; Run tests: pio test -e native
|
||||||
;
|
;
|
||||||
; https://docs.platformio.org/page/projectconf.html
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
@@ -19,6 +21,8 @@ framework = arduino
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
lib_deps =
|
lib_deps =
|
||||||
adafruit/Adafruit GFX Library@^1.10.12
|
adafruit/Adafruit GFX Library@^1.10.12
|
||||||
|
; Tests only run in the native environment, never on the boards
|
||||||
|
test_ignore = *
|
||||||
|
|
||||||
; ---------------------------------------------------------------------------
|
; ---------------------------------------------------------------------------
|
||||||
; Arduino Nano - display driver
|
; Arduino Nano - display driver
|
||||||
@@ -57,3 +61,17 @@ monitor_port = COM11
|
|||||||
extends = env:esp32
|
extends = env:esp32
|
||||||
upload_protocol = esptool
|
upload_protocol = esptool
|
||||||
upload_port = COM11
|
upload_port = COM11
|
||||||
|
|
||||||
|
; ---------------------------------------------------------------------------
|
||||||
|
; Native - unit tests for the Nano firmware on the PC (test/test_nano)
|
||||||
|
; The Arduino core, EEPROM and the display driver are replaced by test/mocks.
|
||||||
|
; ---------------------------------------------------------------------------
|
||||||
|
[env:native]
|
||||||
|
platform = native
|
||||||
|
framework =
|
||||||
|
lib_deps =
|
||||||
|
lib_ignore = BROSE9323
|
||||||
|
test_ignore =
|
||||||
|
test_build_src = yes
|
||||||
|
build_src_filter = +<nano/>
|
||||||
|
build_flags = -std=gnu++17 -I test/mocks
|
||||||
@@ -17,6 +17,14 @@ logic.
|
|||||||
|
|
||||||
The display driver itself is in [lib/BROSE9323](../../lib/BROSE9323/README.md).
|
The display driver itself is in [lib/BROSE9323](../../lib/BROSE9323/README.md).
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
`pio test -e native` runs the unit tests in [test/test_nano](../../test/test_nano/test_main.cpp)
|
||||||
|
on the PC. They feed serial data to the firmware and check what it asks the display to do:
|
||||||
|
every command, bitmaps, bad input, flip time storage and the full-redraw timing. The
|
||||||
|
Arduino core, EEPROM and the display driver are replaced by fakes in
|
||||||
|
[test/mocks](../../test/mocks).
|
||||||
|
|
||||||
## Behaviour
|
## Behaviour
|
||||||
|
|
||||||
- **Boot:** the Nano loads the flip time from EEPROM, sets up the pins and sends `READY`.
|
- **Boot:** the Nano loads the flip time from EEPROM, sets up the pins and sends `READY`.
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Minimal Arduino API for running the Nano code on the PC (native test environment)
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <deque>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#define HEX 16
|
||||||
|
|
||||||
|
class __FlashStringHelper;
|
||||||
|
#define F(s) ((const __FlashStringHelper *)(s))
|
||||||
|
|
||||||
|
// Time is controlled by the tests
|
||||||
|
inline uint32_t fake_millis = 0;
|
||||||
|
inline uint32_t millis() { return fake_millis; }
|
||||||
|
inline void delay(uint32_t ms) { fake_millis += ms; }
|
||||||
|
|
||||||
|
// Serial port: tests push bytes into `in` and read replies from `out`
|
||||||
|
class FakeSerial
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::deque<char> in;
|
||||||
|
std::string out;
|
||||||
|
|
||||||
|
void begin(unsigned long) {}
|
||||||
|
int available() { return in.size(); }
|
||||||
|
int read()
|
||||||
|
{
|
||||||
|
if (in.empty())
|
||||||
|
return -1;
|
||||||
|
char c = in.front();
|
||||||
|
in.pop_front();
|
||||||
|
return (uint8_t)c;
|
||||||
|
}
|
||||||
|
void flush() {}
|
||||||
|
|
||||||
|
void print(const __FlashStringHelper *s) { out += (const char *)s; }
|
||||||
|
void print(const char *s) { out += s; }
|
||||||
|
void print(int v, int base = 10)
|
||||||
|
{
|
||||||
|
char buf[16];
|
||||||
|
snprintf(buf, sizeof(buf), base == HEX ? "%X" : "%d", v);
|
||||||
|
out += buf;
|
||||||
|
}
|
||||||
|
void println(const __FlashStringHelper *s) { print(s); out += "\n"; }
|
||||||
|
void println(const char *s) { print(s); out += "\n"; }
|
||||||
|
void println(unsigned long v) { out += std::to_string(v) + "\n"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
using Print = FakeSerial;
|
||||||
|
|
||||||
|
inline FakeSerial Serial;
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// Fake display driver for the native tests: records every call instead of flipping dots
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
struct DisplayCall
|
||||||
|
{
|
||||||
|
std::string what;
|
||||||
|
int a, b, c;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BROSE9323
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<DisplayCall> calls;
|
||||||
|
bool direct = false;
|
||||||
|
|
||||||
|
BROSE9323(uint8_t, uint8_t, uint8_t, uint16_t) {}
|
||||||
|
|
||||||
|
void begin() { log("begin"); }
|
||||||
|
void display(bool force = false) { log("display", force); }
|
||||||
|
void drawPixel(int16_t x, int16_t y, uint16_t color) { log(direct ? "directPixel" : "pixel", x, y, color); }
|
||||||
|
void fillScreen(uint16_t color) { log("fill", color); }
|
||||||
|
void setDirect(bool d) { direct = d; }
|
||||||
|
void setTiming(uint16_t t) { log("timing", t); }
|
||||||
|
void printBuffer() { log("printBuffer"); }
|
||||||
|
void setCursor(int16_t x, int16_t y) { log("cursor", x, y); }
|
||||||
|
void setTextColor(uint16_t color, uint16_t background) { log("textColor", color, background); }
|
||||||
|
void setTextSize(uint8_t size) { log("textSize", size); }
|
||||||
|
size_t write(uint8_t ch)
|
||||||
|
{
|
||||||
|
log("write", ch);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
void println() { log("newline"); }
|
||||||
|
|
||||||
|
// Helpers for the tests
|
||||||
|
int count(const std::string &what) const
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
for (const DisplayCall &call : calls)
|
||||||
|
n += call.what == what;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
const DisplayCall *last(const std::string &what) const
|
||||||
|
{
|
||||||
|
for (auto it = calls.rbegin(); it != calls.rend(); ++it)
|
||||||
|
if (it->what == what)
|
||||||
|
return &*it;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void log(const char *what, int a = 0, int b = 0, int c = 0) { calls.push_back({what, a, b, c}); }
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// In-memory EEPROM for the native tests (starts erased, all 0xFF like a new chip)
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
class FakeEEPROM
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
uint8_t mem[1024];
|
||||||
|
|
||||||
|
FakeEEPROM() { erase(); }
|
||||||
|
void erase() { memset(mem, 0xFF, sizeof(mem)); }
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T &get(int addr, T &t)
|
||||||
|
{
|
||||||
|
memcpy(&t, mem + addr, sizeof(T));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
const T &put(int addr, const T &t)
|
||||||
|
{
|
||||||
|
memcpy(mem + addr, &t, sizeof(T));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline FakeEEPROM EEPROM;
|
||||||
@@ -0,0 +1,321 @@
|
|||||||
|
// Tests for the Nano firmware (src/nano), run on the PC: pio test -e native
|
||||||
|
//
|
||||||
|
// The real Arduino, EEPROM and display driver are replaced by the fakes in test/mocks.
|
||||||
|
// Tests push bytes into Serial.in, call loop() and check what the display was asked to do.
|
||||||
|
|
||||||
|
#include <unity.h>
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "BROSE9323.h"
|
||||||
|
#include "EEPROM.h"
|
||||||
|
|
||||||
|
// From src/nano/main.cpp
|
||||||
|
extern BROSE9323 display;
|
||||||
|
extern uint32_t last_refresh;
|
||||||
|
void setup();
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
// Send bytes to the Nano and let it process them
|
||||||
|
static void send(const std::string &data)
|
||||||
|
{
|
||||||
|
for (char c : data)
|
||||||
|
Serial.in.push_back(c);
|
||||||
|
loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool replied(const char *text)
|
||||||
|
{
|
||||||
|
return Serial.out.find(text) != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUp()
|
||||||
|
{
|
||||||
|
// Finish any half-received line from the previous test
|
||||||
|
send("\n");
|
||||||
|
fake_millis += 1;
|
||||||
|
last_refresh = fake_millis; // no full redraw due unless a test asks for one
|
||||||
|
display.calls.clear();
|
||||||
|
display.direct = false;
|
||||||
|
Serial.out.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tearDown() {}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Boot
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void test_boot_does_not_flip_dots()
|
||||||
|
{
|
||||||
|
EEPROM.erase();
|
||||||
|
setup();
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("begin"));
|
||||||
|
TEST_ASSERT_EQUAL(0, display.count("display"));
|
||||||
|
TEST_ASSERT_EQUAL(0, display.count("fill"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_boot_sends_ready()
|
||||||
|
{
|
||||||
|
setup();
|
||||||
|
TEST_ASSERT_TRUE(replied("READY\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_boot_uses_default_flip_time_with_empty_eeprom()
|
||||||
|
{
|
||||||
|
EEPROM.erase();
|
||||||
|
setup();
|
||||||
|
TEST_ASSERT_EQUAL(200, display.last("timing")->a);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Commands
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void test_pixel_runs_without_waiting_for_next_command()
|
||||||
|
{
|
||||||
|
send("s0a051\n");
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("pixel"));
|
||||||
|
TEST_ASSERT_EQUAL(10, display.last("pixel")->a);
|
||||||
|
TEST_ASSERT_EQUAL(5, display.last("pixel")->b);
|
||||||
|
TEST_ASSERT_EQUAL(1, display.last("pixel")->c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_direct_pixel_flips_immediately_and_resets_direct_mode()
|
||||||
|
{
|
||||||
|
send("S010200\n");
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("directPixel"));
|
||||||
|
TEST_ASSERT_FALSE(display.direct);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_fill()
|
||||||
|
{
|
||||||
|
send("F1\n");
|
||||||
|
TEST_ASSERT_EQUAL(1, display.last("fill")->a);
|
||||||
|
send("F0\n");
|
||||||
|
TEST_ASSERT_EQUAL(0, display.last("fill")->a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_display()
|
||||||
|
{
|
||||||
|
send("D\n");
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("display"));
|
||||||
|
TEST_ASSERT_EQUAL(0, display.last("display")->a); // not forced
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_lowercase_and_uppercase_commands()
|
||||||
|
{
|
||||||
|
send("f1\nd\n");
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("fill"));
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("display"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_command_split_over_several_reads_with_crlf()
|
||||||
|
{
|
||||||
|
send("s0");
|
||||||
|
send("1");
|
||||||
|
send("021\r\n");
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("pixel"));
|
||||||
|
TEST_ASSERT_EQUAL(1, display.last("pixel")->a);
|
||||||
|
TEST_ASSERT_EQUAL(2, display.last("pixel")->b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_print_text_with_newline_escape()
|
||||||
|
{
|
||||||
|
send("P0102115\\nab\n");
|
||||||
|
TEST_ASSERT_EQUAL(1, display.last("cursor")->a);
|
||||||
|
TEST_ASSERT_EQUAL(2, display.last("cursor")->b);
|
||||||
|
TEST_ASSERT_EQUAL(1, display.last("textColor")->a);
|
||||||
|
TEST_ASSERT_EQUAL(0, display.last("textColor")->b);
|
||||||
|
TEST_ASSERT_EQUAL(1, display.last("textSize")->a);
|
||||||
|
TEST_ASSERT_EQUAL(3, display.count("write")); // '5', 'a', 'b' and no garbage
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("newline"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_print_buffer()
|
||||||
|
{
|
||||||
|
send("R\n");
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("printBuffer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Bitmaps
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void test_bitmap_pixels()
|
||||||
|
{
|
||||||
|
// 8x2 at (4,3), color 1, rows F0 and 81
|
||||||
|
send("B040308021F081\n");
|
||||||
|
const int expected[][2] = {{4, 3}, {5, 3}, {6, 3}, {7, 3}, {4, 4}, {11, 4}};
|
||||||
|
TEST_ASSERT_EQUAL(6, display.count("pixel"));
|
||||||
|
int i = 0;
|
||||||
|
for (const DisplayCall &call : display.calls)
|
||||||
|
{
|
||||||
|
if (call.what != "pixel")
|
||||||
|
continue;
|
||||||
|
TEST_ASSERT_EQUAL(expected[i][0], call.a);
|
||||||
|
TEST_ASSERT_EQUAL(expected[i][1], call.b);
|
||||||
|
TEST_ASSERT_EQUAL(1, call.c);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_full_screen_bitmap_is_streamed()
|
||||||
|
{
|
||||||
|
// 84x19 = 399 hex digits, much longer than the 64 character line buffer
|
||||||
|
send("B000054131" + std::string(399, 'F') + "\n");
|
||||||
|
TEST_ASSERT_EQUAL(84 * 19, display.count("pixel"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_invalid_bitmap_data_is_not_run_as_commands()
|
||||||
|
{
|
||||||
|
// width 5 is invalid; the data contains the command letters B, D and F
|
||||||
|
send("B0000050100BDFDFDF\nD\n");
|
||||||
|
TEST_ASSERT_TRUE(replied("ERR"));
|
||||||
|
TEST_ASSERT_EQUAL(0, display.count("fill"));
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("display"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_incomplete_bitmap_header()
|
||||||
|
{
|
||||||
|
send("B00\nD\n");
|
||||||
|
TEST_ASSERT_TRUE(replied("ERR"));
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("display"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Recovering from bad input
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void test_too_long_line_is_rejected()
|
||||||
|
{
|
||||||
|
send(std::string(200, 'x') + "\nF1\n");
|
||||||
|
TEST_ASSERT_TRUE(replied("Line too long"));
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("fill"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_truncated_command_is_ignored()
|
||||||
|
{
|
||||||
|
send("s01\nF1\n");
|
||||||
|
TEST_ASSERT_EQUAL(0, display.count("pixel"));
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("fill"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_unknown_command_is_ignored()
|
||||||
|
{
|
||||||
|
send("xyz\nF1\n");
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("fill"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Flip time
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void test_timing_is_applied_immediately()
|
||||||
|
{
|
||||||
|
send("T225\n");
|
||||||
|
TEST_ASSERT_EQUAL(225, display.last("timing")->a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_timing_above_255_is_stored_and_restored()
|
||||||
|
{
|
||||||
|
send("T300\n");
|
||||||
|
TEST_ASSERT_EQUAL(300, display.last("timing")->a);
|
||||||
|
display.calls.clear();
|
||||||
|
setup(); // reboot
|
||||||
|
TEST_ASSERT_EQUAL(300, display.last("timing")->a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_timing_out_of_range_is_rejected()
|
||||||
|
{
|
||||||
|
send("T5\n");
|
||||||
|
send("T99999\n");
|
||||||
|
TEST_ASSERT_EQUAL(0, display.count("timing"));
|
||||||
|
TEST_ASSERT_TRUE(replied("ERR"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Periodic full redraw
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void test_refresh_waits_for_end_of_frame()
|
||||||
|
{
|
||||||
|
last_refresh = fake_millis;
|
||||||
|
fake_millis += 120000;
|
||||||
|
send("s000001\n");
|
||||||
|
TEST_ASSERT_EQUAL(0, display.count("display"));
|
||||||
|
send("D\n");
|
||||||
|
TEST_ASSERT_EQUAL(2, display.count("display"));
|
||||||
|
TEST_ASSERT_EQUAL(1, display.calls.back().a); // forced full redraw after the frame
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_refresh_only_once_per_interval()
|
||||||
|
{
|
||||||
|
last_refresh = fake_millis;
|
||||||
|
fake_millis += 120000;
|
||||||
|
send("D\n");
|
||||||
|
display.calls.clear();
|
||||||
|
send("D\n");
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("display"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_refresh_when_serial_is_idle()
|
||||||
|
{
|
||||||
|
last_refresh = fake_millis;
|
||||||
|
fake_millis += 120000;
|
||||||
|
loop();
|
||||||
|
TEST_ASSERT_EQUAL(1, display.count("display"));
|
||||||
|
TEST_ASSERT_EQUAL(1, display.last("display")->a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_no_refresh_storm_after_millis_wraps()
|
||||||
|
{
|
||||||
|
fake_millis = 0xFFFFFFFFu - 1000;
|
||||||
|
last_refresh = fake_millis;
|
||||||
|
fake_millis += 5000; // wraps past 0
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
send("D\n");
|
||||||
|
TEST_ASSERT_EQUAL(5, display.count("display"));
|
||||||
|
|
||||||
|
fake_millis += 120000;
|
||||||
|
display.calls.clear();
|
||||||
|
send("D\n");
|
||||||
|
TEST_ASSERT_EQUAL(2, display.count("display"));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
UNITY_BEGIN();
|
||||||
|
|
||||||
|
RUN_TEST(test_boot_does_not_flip_dots);
|
||||||
|
RUN_TEST(test_boot_sends_ready);
|
||||||
|
RUN_TEST(test_boot_uses_default_flip_time_with_empty_eeprom);
|
||||||
|
|
||||||
|
RUN_TEST(test_pixel_runs_without_waiting_for_next_command);
|
||||||
|
RUN_TEST(test_direct_pixel_flips_immediately_and_resets_direct_mode);
|
||||||
|
RUN_TEST(test_fill);
|
||||||
|
RUN_TEST(test_display);
|
||||||
|
RUN_TEST(test_lowercase_and_uppercase_commands);
|
||||||
|
RUN_TEST(test_command_split_over_several_reads_with_crlf);
|
||||||
|
RUN_TEST(test_print_text_with_newline_escape);
|
||||||
|
RUN_TEST(test_print_buffer);
|
||||||
|
|
||||||
|
RUN_TEST(test_bitmap_pixels);
|
||||||
|
RUN_TEST(test_full_screen_bitmap_is_streamed);
|
||||||
|
RUN_TEST(test_invalid_bitmap_data_is_not_run_as_commands);
|
||||||
|
RUN_TEST(test_incomplete_bitmap_header);
|
||||||
|
|
||||||
|
RUN_TEST(test_too_long_line_is_rejected);
|
||||||
|
RUN_TEST(test_truncated_command_is_ignored);
|
||||||
|
RUN_TEST(test_unknown_command_is_ignored);
|
||||||
|
|
||||||
|
RUN_TEST(test_timing_is_applied_immediately);
|
||||||
|
RUN_TEST(test_timing_above_255_is_stored_and_restored);
|
||||||
|
RUN_TEST(test_timing_out_of_range_is_rejected);
|
||||||
|
|
||||||
|
RUN_TEST(test_refresh_waits_for_end_of_frame);
|
||||||
|
RUN_TEST(test_refresh_only_once_per_interval);
|
||||||
|
RUN_TEST(test_refresh_when_serial_is_idle);
|
||||||
|
RUN_TEST(test_no_refresh_storm_after_millis_wraps);
|
||||||
|
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
Reference in new issue
Block a user