Skip to content

Commit

Permalink
Enable unit tests in CI/CD
Browse files Browse the repository at this point in the history
Tests were re-written into the Unity framework
supported by PlatformIO. They are run inside an ESP32
QEMU simulator.
  • Loading branch information
vzahradnik committed Aug 3, 2024
1 parent 54ded14 commit 3480ae9
Show file tree
Hide file tree
Showing 10 changed files with 495 additions and 362 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: PlatformIO CI
name: Build
on: [push]

jobs:
Expand Down
62 changes: 62 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Test
on: [push]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cache/pip
~/.platformio/.cache
key: ${{ runner.os }}-pio
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y libsdl2-2.0-0
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install PlatformIO Core
run: pip install --upgrade platformio
- name: Set up QEMU
id: setup-qemu
run: |
if [[ "$(uname -m)" == "x86_64" ]]; then
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"
elif [[ "$(uname -m)" == "aarch64" ]]; then
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"
else
echo "Unsupported architecture: $(uname -m)"
exit 1
fi
wget $QEMU_URL -O qemu.tar.xz
mkdir -p qemu
tar -xf qemu.tar.xz -C qemu --strip-components=1
sudo mv qemu /usr/local/qemu
- name: Add QEMU to PATH
run: echo "/usr/local/qemu/bin" >> $GITHUB_PATH

- name: Run unit tests
run: pio test --without-uploading --project-conf=platformio-test.ini

static-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cache/pip
~/.platformio/.cache
key: ${{ runner.os }}-pio
- uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install PlatformIO Core
run: pip install --upgrade platformio

- name: Run static analysis
run: pio check
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# TcUnicode support for Adafruit_GFX, U8G2, TFT_eSPI, tcMenu.
[![PlatformIO](https://github.com/TcMenu/tcUnicodeHelper/actions/workflows/platformio.yml/badge.svg)](https://github.com/TcMenu/tcUnicodeHelper/actions/workflows/platformio.yml)
[![Build](https://github.com/TcMenu/tcUnicodeHelper/actions/workflows/build.yml/badge.svg)](https://github.com/TcMenu/tcUnicodeHelper/actions/workflows/build.yml)
[![Test](https://github.com/TcMenu/tcUnicodeHelper/actions/workflows/test.yml/badge.svg)](https://github.com/TcMenu/tcUnicodeHelper/actions/workflows/test.yml)
[![License: Apache 2.0](https://img.shields.io/badge/license-Apache--2.0-green.svg)](https://github.com/TcMenu/tcUnicodeHelper/blob/main/LICENSE)
[![GitHub release](https://img.shields.io/github/release/TcMenu/tcUnicodeHelper.svg?maxAge=3600)](https://github.com/TcMenu/tcUnicodeHelper/releases)
[![davetcc](https://img.shields.io/badge/davetcc-dev-blue.svg)](https://github.com/davetcc)
Expand Down
43 changes: 43 additions & 0 deletions merge-bin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/python3

# Adds PlatformIO post-processing to merge all the ESP flash images into a single image.

import os

Import("env", "projenv")

board_config = env.BoardConfig()
firmware_bin = "${BUILD_DIR}/${PROGNAME}.bin"
merged_bin = os.environ.get("MERGED_BIN_PATH", "${BUILD_DIR}/${PROGNAME}-merged.bin")


def merge_bin_action(source, target, env):
flash_images = [
*env.Flatten(env.get("FLASH_EXTRA_IMAGES", [])),
"$ESP32_APP_OFFSET",
source[0].get_abspath(),
]
merge_cmd = " ".join(
[
'"$PYTHONEXE"',
'"$OBJCOPY"',
"--chip",
board_config.get("build.mcu", "esp32"),
"merge_bin",
"-o",
merged_bin,
"--flash_mode",
board_config.get("build.flash_mode", "dio"),
"--flash_freq",
"${__get_board_f_flash(__env__)}",
"--flash_size",
board_config.get("upload.flash_size", "4MB"),
"--fill-flash-size",
board_config.get("upload.flash_size", "4MB"),
*flash_images,
]
)
env.Execute(merge_cmd)


env.AddPostAction("buildprog", merge_bin_action)
17 changes: 17 additions & 0 deletions platformio-test.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[env:esp32dev]
platform = espressif32
framework = arduino
board = esp32dev
extra_scripts = post:merge-bin.py

lib_deps =
davetcc/IoAbstraction@^4.0.2
tcUnicodeHelper

test_testing_command =
qemu-system-xtensa
-nographic
-machine
esp32
-drive
file=${platformio.build_dir}/${this.__env__}/firmware-merged.bin,if=mtd,format=raw
1 change: 1 addition & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ lib_deps =
adafruit/Adafruit BusIO@^1.16.1
SPI
Wire
tcUnicodeHelper

[env:megaatmega2560]
platform = atmelavr
Expand Down
149 changes: 149 additions & 0 deletions test/test_unicode_font/unicodeFontTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@

#include <Arduino.h>
#include <unity.h>
#include <deque>
#include <Fonts/OpenSansCyrillicLatin18.h>
#include <Fonts/RobotoMedium24.h>
#include <tcUnicodeHelper.h>
#include <IoLogging.h>

class UnitTestPlotter : public TextPlotPipeline {
private:
std::deque<Coord> pixelsDrawn;
static const size_t max_size = 32;
uint32_t col = 0;
Coord where = {0,0};
public:
UnitTestPlotter() = default;
~UnitTestPlotter() = default;

void drawPixel(uint16_t x, uint16_t y, uint32_t color) override {
if (pixelsDrawn.size() == max_size) {
pixelsDrawn.pop_front();
}
pixelsDrawn.push_back(Coord(x, y));
}

void setCursor(const Coord &p) override {
where = p;
}

Coord getCursor() override {
return where;
}

Coord getDimensions() override {
return Coord(320, 200);
}

void init() {
pixelsDrawn.clear();
where = {0,0};
}
} unitTestPlotter;

UnicodeFontHandler* handler = nullptr;

void setUp() {
unitTestPlotter.init();
handler = new UnicodeFontHandler(&unitTestPlotter, ENCMODE_UTF8);
handler->setFont(OpenSansCyrillicLatin18);
handler->setDrawColor(20);
}

void tearDown() {
delete handler;
}

bool checkGlyph(uint32_t code, uint32_t bmpOffset, int width, int height, int xAdvance, int xOffset, int yOffset) {
GlyphWithBitmap glyphWithBitmap;
if(handler->findCharInFont(code, glyphWithBitmap)) {
const UnicodeFontGlyph *glyph = glyphWithBitmap.getGlyph();
bool success = true;
if(bmpOffset != glyph->relativeBmpOffset) {
serlogF4(SER_DEBUG, "Bmp offset out ", code, bmpOffset, glyph->relativeBmpOffset);
success = false;
}
if(width != glyph->width || height != glyph->height) {
serlogF4(SER_DEBUG, "Bmp width out ", code, width, glyph->width);
serlogF3(SER_DEBUG, "Bmp height ", height, glyph->height);
success = false;
}
if(xOffset != glyph->xOffset || yOffset != glyph->yOffset) {
serlogF4(SER_DEBUG, "Bmp xoffs out ", code, xOffset, glyph->xOffset);
serlogF3(SER_DEBUG, "Bmp yoffs ", yOffset, glyph->yOffset);
success = false;
}
if(xAdvance != glyph->xAdvance) {
serlogF4(SER_DEBUG, "Bmp xadv out ", code, xAdvance, glyph->xAdvance);
success = false;
}

return success;
} else {
serlogF2(SER_DEBUG, "Glyph not found ", code);
return false;
}
}

void test_TextExtents() {
int bl;
handler->setFont(OpenSansCyrillicLatin18);
Coord coord = handler->textExtents("Abc", &bl, false);
TEST_ASSERT_EQUAL_INT16(31, coord.x);
TEST_ASSERT_EQUAL_INT16(24, coord.y);

handler->setFont(RobotoMedium24pt);
coord = handler->textExtents("Abc", &bl, false);
TEST_ASSERT_EQUAL_INT16(43, coord.x);
TEST_ASSERT_EQUAL_INT16(28, coord.y);
}

void test_GetGlyphOnEachRange() {
TEST_ASSERT_TRUE(checkGlyph(65, 320, 11, 13, 11, 0, -18));
TEST_ASSERT_TRUE(checkGlyph(55 + 128, 198, 2, 2, 5, 1, -12));
TEST_ASSERT_TRUE(checkGlyph(17 + 1024, 285, 8, 13, 11, 2, -18));
}

void test_ReadingEveryGlyphInRange() {
GlyphWithBitmap glyphWithBitmap;

// test all known characters work
for (int i = 32; i < 127; i++) {
serlogF2(SER_DEBUG, "Test character = ", i);
TEST_ASSERT_TRUE(handler->findCharInFont(i, glyphWithBitmap));
TEST_ASSERT_NOT_NULL(glyphWithBitmap.getGlyph());
TEST_ASSERT_NOT_NULL(glyphWithBitmap.getBitmapData());
}

// test a few that should fail
TEST_ASSERT_FALSE(handler->findCharInFont(0, glyphWithBitmap));
TEST_ASSERT_FALSE(handler->findCharInFont(5, glyphWithBitmap));
TEST_ASSERT_FALSE(handler->findCharInFont(10, glyphWithBitmap));
TEST_ASSERT_FALSE(handler->findCharInFont(0xFFFF, glyphWithBitmap));
}

void test_AdafruitFont() {
GlyphWithBitmap glyphWithBitmap;
handler->setFont(RobotoMedium24pt);

// test a few that should fail
TEST_ASSERT_TRUE(checkGlyph(32, 0, 0, 0, 6, 0, -28));
TEST_ASSERT_TRUE(checkGlyph(48, 243, 12, 17, 14, 1, -23));
TEST_ASSERT_TRUE(checkGlyph(65, 611, 16, 17, 16, 0, -23));
TEST_ASSERT_TRUE(checkGlyph(126, 1990, 14, 5, 16, 1, -16));
TEST_ASSERT_FALSE(handler->findCharInFont(5, glyphWithBitmap));
TEST_ASSERT_FALSE(handler->findCharInFont(127, glyphWithBitmap));
TEST_ASSERT_FALSE(handler->findCharInFont(31, glyphWithBitmap));
}

void setup() {
UNITY_BEGIN();
RUN_TEST(test_TextExtents);
RUN_TEST(test_GetGlyphOnEachRange);
RUN_TEST(test_ReadingEveryGlyphInRange);
RUN_TEST(test_AdafruitFont);
UNITY_END();
}

void loop() {}
Loading

0 comments on commit 3480ae9

Please sign in to comment.