Skip to content

Commit 3480ae9

Browse files
committed
Enable unit tests in CI/CD
Tests were re-written into the Unity framework supported by PlatformIO. They are run inside an ESP32 QEMU simulator.
1 parent 54ded14 commit 3480ae9

File tree

10 files changed

+495
-362
lines changed

10 files changed

+495
-362
lines changed

.github/workflows/platformio.yml renamed to .github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: PlatformIO CI
1+
name: Build
22
on: [push]
33

44
jobs:

.github/workflows/test.yml

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Test
2+
on: [push]
3+
4+
jobs:
5+
test:
6+
runs-on: ubuntu-latest
7+
steps:
8+
- uses: actions/checkout@v4
9+
- uses: actions/cache@v4
10+
with:
11+
path: |
12+
~/.cache/pip
13+
~/.platformio/.cache
14+
key: ${{ runner.os }}-pio
15+
- name: Install dependencies
16+
run: sudo apt-get update && sudo apt-get install -y libsdl2-2.0-0
17+
- uses: actions/setup-python@v5
18+
with:
19+
python-version: '3.12'
20+
- name: Install PlatformIO Core
21+
run: pip install --upgrade platformio
22+
- name: Set up QEMU
23+
id: setup-qemu
24+
run: |
25+
if [[ "$(uname -m)" == "x86_64" ]]; then
26+
QEMU_URL="https://github.com/espressif/qemu/releases/download/esp-develop-8.2.0-20240122/qemu-xtensa-softmmu-esp_develop_8.2.0_20240122-x86_64-linux-gnu.tar.xz"
27+
elif [[ "$(uname -m)" == "aarch64" ]]; then
28+
QEMU_URL="https://github.com/espressif/qemu/releases/download/esp-develop-8.2.0-20240122/qemu-xtensa-softmmu-esp_develop_8.2.0_20240122-aarch64-linux-gnu.tar.xz"
29+
else
30+
echo "Unsupported architecture: $(uname -m)"
31+
exit 1
32+
fi
33+
wget $QEMU_URL -O qemu.tar.xz
34+
mkdir -p qemu
35+
tar -xf qemu.tar.xz -C qemu --strip-components=1
36+
sudo mv qemu /usr/local/qemu
37+
38+
- name: Add QEMU to PATH
39+
run: echo "/usr/local/qemu/bin" >> $GITHUB_PATH
40+
41+
- name: Run unit tests
42+
run: pio test --without-uploading --project-conf=platformio-test.ini
43+
44+
static-analysis:
45+
runs-on: ubuntu-latest
46+
steps:
47+
- uses: actions/checkout@v4
48+
- uses: actions/cache@v4
49+
with:
50+
path: |
51+
~/.cache/pip
52+
~/.platformio/.cache
53+
key: ${{ runner.os }}-pio
54+
- uses: actions/setup-python@v5
55+
with:
56+
python-version: '3.12'
57+
58+
- name: Install PlatformIO Core
59+
run: pip install --upgrade platformio
60+
61+
- name: Run static analysis
62+
run: pio check

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# TcUnicode support for Adafruit_GFX, U8G2, TFT_eSPI, tcMenu.
2-
[![PlatformIO](https://github.com/TcMenu/tcUnicodeHelper/actions/workflows/platformio.yml/badge.svg)](https://github.com/TcMenu/tcUnicodeHelper/actions/workflows/platformio.yml)
2+
[![Build](https://github.com/TcMenu/tcUnicodeHelper/actions/workflows/build.yml/badge.svg)](https://github.com/TcMenu/tcUnicodeHelper/actions/workflows/build.yml)
3+
[![Test](https://github.com/TcMenu/tcUnicodeHelper/actions/workflows/test.yml/badge.svg)](https://github.com/TcMenu/tcUnicodeHelper/actions/workflows/test.yml)
34
[![License: Apache 2.0](https://img.shields.io/badge/license-Apache--2.0-green.svg)](https://github.com/TcMenu/tcUnicodeHelper/blob/main/LICENSE)
45
[![GitHub release](https://img.shields.io/github/release/TcMenu/tcUnicodeHelper.svg?maxAge=3600)](https://github.com/TcMenu/tcUnicodeHelper/releases)
56
[![davetcc](https://img.shields.io/badge/davetcc-dev-blue.svg)](https://github.com/davetcc)

merge-bin.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/python3
2+
3+
# Adds PlatformIO post-processing to merge all the ESP flash images into a single image.
4+
5+
import os
6+
7+
Import("env", "projenv")
8+
9+
board_config = env.BoardConfig()
10+
firmware_bin = "${BUILD_DIR}/${PROGNAME}.bin"
11+
merged_bin = os.environ.get("MERGED_BIN_PATH", "${BUILD_DIR}/${PROGNAME}-merged.bin")
12+
13+
14+
def merge_bin_action(source, target, env):
15+
flash_images = [
16+
*env.Flatten(env.get("FLASH_EXTRA_IMAGES", [])),
17+
"$ESP32_APP_OFFSET",
18+
source[0].get_abspath(),
19+
]
20+
merge_cmd = " ".join(
21+
[
22+
'"$PYTHONEXE"',
23+
'"$OBJCOPY"',
24+
"--chip",
25+
board_config.get("build.mcu", "esp32"),
26+
"merge_bin",
27+
"-o",
28+
merged_bin,
29+
"--flash_mode",
30+
board_config.get("build.flash_mode", "dio"),
31+
"--flash_freq",
32+
"${__get_board_f_flash(__env__)}",
33+
"--flash_size",
34+
board_config.get("upload.flash_size", "4MB"),
35+
"--fill-flash-size",
36+
board_config.get("upload.flash_size", "4MB"),
37+
*flash_images,
38+
]
39+
)
40+
env.Execute(merge_cmd)
41+
42+
43+
env.AddPostAction("buildprog", merge_bin_action)

platformio-test.ini

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[env:esp32dev]
2+
platform = espressif32
3+
framework = arduino
4+
board = esp32dev
5+
extra_scripts = post:merge-bin.py
6+
7+
lib_deps =
8+
davetcc/IoAbstraction@^4.0.2
9+
tcUnicodeHelper
10+
11+
test_testing_command =
12+
qemu-system-xtensa
13+
-nographic
14+
-machine
15+
esp32
16+
-drive
17+
file=${platformio.build_dir}/${this.__env__}/firmware-merged.bin,if=mtd,format=raw

platformio.ini

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ lib_deps =
77
adafruit/Adafruit BusIO@^1.16.1
88
SPI
99
Wire
10+
tcUnicodeHelper
1011

1112
[env:megaatmega2560]
1213
platform = atmelavr
+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
2+
#include <Arduino.h>
3+
#include <unity.h>
4+
#include <deque>
5+
#include <Fonts/OpenSansCyrillicLatin18.h>
6+
#include <Fonts/RobotoMedium24.h>
7+
#include <tcUnicodeHelper.h>
8+
#include <IoLogging.h>
9+
10+
class UnitTestPlotter : public TextPlotPipeline {
11+
private:
12+
std::deque<Coord> pixelsDrawn;
13+
static const size_t max_size = 32;
14+
uint32_t col = 0;
15+
Coord where = {0,0};
16+
public:
17+
UnitTestPlotter() = default;
18+
~UnitTestPlotter() = default;
19+
20+
void drawPixel(uint16_t x, uint16_t y, uint32_t color) override {
21+
if (pixelsDrawn.size() == max_size) {
22+
pixelsDrawn.pop_front();
23+
}
24+
pixelsDrawn.push_back(Coord(x, y));
25+
}
26+
27+
void setCursor(const Coord &p) override {
28+
where = p;
29+
}
30+
31+
Coord getCursor() override {
32+
return where;
33+
}
34+
35+
Coord getDimensions() override {
36+
return Coord(320, 200);
37+
}
38+
39+
void init() {
40+
pixelsDrawn.clear();
41+
where = {0,0};
42+
}
43+
} unitTestPlotter;
44+
45+
UnicodeFontHandler* handler = nullptr;
46+
47+
void setUp() {
48+
unitTestPlotter.init();
49+
handler = new UnicodeFontHandler(&unitTestPlotter, ENCMODE_UTF8);
50+
handler->setFont(OpenSansCyrillicLatin18);
51+
handler->setDrawColor(20);
52+
}
53+
54+
void tearDown() {
55+
delete handler;
56+
}
57+
58+
bool checkGlyph(uint32_t code, uint32_t bmpOffset, int width, int height, int xAdvance, int xOffset, int yOffset) {
59+
GlyphWithBitmap glyphWithBitmap;
60+
if(handler->findCharInFont(code, glyphWithBitmap)) {
61+
const UnicodeFontGlyph *glyph = glyphWithBitmap.getGlyph();
62+
bool success = true;
63+
if(bmpOffset != glyph->relativeBmpOffset) {
64+
serlogF4(SER_DEBUG, "Bmp offset out ", code, bmpOffset, glyph->relativeBmpOffset);
65+
success = false;
66+
}
67+
if(width != glyph->width || height != glyph->height) {
68+
serlogF4(SER_DEBUG, "Bmp width out ", code, width, glyph->width);
69+
serlogF3(SER_DEBUG, "Bmp height ", height, glyph->height);
70+
success = false;
71+
}
72+
if(xOffset != glyph->xOffset || yOffset != glyph->yOffset) {
73+
serlogF4(SER_DEBUG, "Bmp xoffs out ", code, xOffset, glyph->xOffset);
74+
serlogF3(SER_DEBUG, "Bmp yoffs ", yOffset, glyph->yOffset);
75+
success = false;
76+
}
77+
if(xAdvance != glyph->xAdvance) {
78+
serlogF4(SER_DEBUG, "Bmp xadv out ", code, xAdvance, glyph->xAdvance);
79+
success = false;
80+
}
81+
82+
return success;
83+
} else {
84+
serlogF2(SER_DEBUG, "Glyph not found ", code);
85+
return false;
86+
}
87+
}
88+
89+
void test_TextExtents() {
90+
int bl;
91+
handler->setFont(OpenSansCyrillicLatin18);
92+
Coord coord = handler->textExtents("Abc", &bl, false);
93+
TEST_ASSERT_EQUAL_INT16(31, coord.x);
94+
TEST_ASSERT_EQUAL_INT16(24, coord.y);
95+
96+
handler->setFont(RobotoMedium24pt);
97+
coord = handler->textExtents("Abc", &bl, false);
98+
TEST_ASSERT_EQUAL_INT16(43, coord.x);
99+
TEST_ASSERT_EQUAL_INT16(28, coord.y);
100+
}
101+
102+
void test_GetGlyphOnEachRange() {
103+
TEST_ASSERT_TRUE(checkGlyph(65, 320, 11, 13, 11, 0, -18));
104+
TEST_ASSERT_TRUE(checkGlyph(55 + 128, 198, 2, 2, 5, 1, -12));
105+
TEST_ASSERT_TRUE(checkGlyph(17 + 1024, 285, 8, 13, 11, 2, -18));
106+
}
107+
108+
void test_ReadingEveryGlyphInRange() {
109+
GlyphWithBitmap glyphWithBitmap;
110+
111+
// test all known characters work
112+
for (int i = 32; i < 127; i++) {
113+
serlogF2(SER_DEBUG, "Test character = ", i);
114+
TEST_ASSERT_TRUE(handler->findCharInFont(i, glyphWithBitmap));
115+
TEST_ASSERT_NOT_NULL(glyphWithBitmap.getGlyph());
116+
TEST_ASSERT_NOT_NULL(glyphWithBitmap.getBitmapData());
117+
}
118+
119+
// test a few that should fail
120+
TEST_ASSERT_FALSE(handler->findCharInFont(0, glyphWithBitmap));
121+
TEST_ASSERT_FALSE(handler->findCharInFont(5, glyphWithBitmap));
122+
TEST_ASSERT_FALSE(handler->findCharInFont(10, glyphWithBitmap));
123+
TEST_ASSERT_FALSE(handler->findCharInFont(0xFFFF, glyphWithBitmap));
124+
}
125+
126+
void test_AdafruitFont() {
127+
GlyphWithBitmap glyphWithBitmap;
128+
handler->setFont(RobotoMedium24pt);
129+
130+
// test a few that should fail
131+
TEST_ASSERT_TRUE(checkGlyph(32, 0, 0, 0, 6, 0, -28));
132+
TEST_ASSERT_TRUE(checkGlyph(48, 243, 12, 17, 14, 1, -23));
133+
TEST_ASSERT_TRUE(checkGlyph(65, 611, 16, 17, 16, 0, -23));
134+
TEST_ASSERT_TRUE(checkGlyph(126, 1990, 14, 5, 16, 1, -16));
135+
TEST_ASSERT_FALSE(handler->findCharInFont(5, glyphWithBitmap));
136+
TEST_ASSERT_FALSE(handler->findCharInFont(127, glyphWithBitmap));
137+
TEST_ASSERT_FALSE(handler->findCharInFont(31, glyphWithBitmap));
138+
}
139+
140+
void setup() {
141+
UNITY_BEGIN();
142+
RUN_TEST(test_TextExtents);
143+
RUN_TEST(test_GetGlyphOnEachRange);
144+
RUN_TEST(test_ReadingEveryGlyphInRange);
145+
RUN_TEST(test_AdafruitFont);
146+
UNITY_END();
147+
}
148+
149+
void loop() {}

0 commit comments

Comments
 (0)