From 3480ae94dbde139c3272cbbb97d5b75941becdfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Z=C3=A1hradn=C3=ADk?= Date: Fri, 2 Aug 2024 19:05:44 +0200 Subject: [PATCH] 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. --- .../workflows/{platformio.yml => build.yml} | 2 +- .github/workflows/test.yml | 62 +++++ README.md | 3 +- merge-bin.py | 43 ++++ platformio-test.ini | 17 ++ platformio.ini | 1 + test/test_unicode_font/unicodeFontTests.cpp | 149 ++++++++++++ test/test_utf8_processing/utf8Processing.cpp | 220 +++++++++++++++++ tests/utf8Processing/unicodeFontTests.cpp | 138 ----------- tests/utf8Processing/utf8Processing.cpp | 222 ------------------ 10 files changed, 495 insertions(+), 362 deletions(-) rename .github/workflows/{platformio.yml => build.yml} (98%) create mode 100644 .github/workflows/test.yml create mode 100644 merge-bin.py create mode 100644 platformio-test.ini create mode 100644 test/test_unicode_font/unicodeFontTests.cpp create mode 100644 test/test_utf8_processing/utf8Processing.cpp delete mode 100644 tests/utf8Processing/unicodeFontTests.cpp delete mode 100644 tests/utf8Processing/utf8Processing.cpp diff --git a/.github/workflows/platformio.yml b/.github/workflows/build.yml similarity index 98% rename from .github/workflows/platformio.yml rename to .github/workflows/build.yml index 4cce1b1..534859d 100644 --- a/.github/workflows/platformio.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: PlatformIO CI +name: Build on: [push] jobs: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..afce0cd --- /dev/null +++ b/.github/workflows/test.yml @@ -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 diff --git a/README.md b/README.md index 66e1993..fc777a2 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/merge-bin.py b/merge-bin.py new file mode 100644 index 0000000..a063ab9 --- /dev/null +++ b/merge-bin.py @@ -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) diff --git a/platformio-test.ini b/platformio-test.ini new file mode 100644 index 0000000..a79a02f --- /dev/null +++ b/platformio-test.ini @@ -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 diff --git a/platformio.ini b/platformio.ini index 2a0a0e5..f4f763f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -7,6 +7,7 @@ lib_deps = adafruit/Adafruit BusIO@^1.16.1 SPI Wire + tcUnicodeHelper [env:megaatmega2560] platform = atmelavr diff --git a/test/test_unicode_font/unicodeFontTests.cpp b/test/test_unicode_font/unicodeFontTests.cpp new file mode 100644 index 0000000..780f686 --- /dev/null +++ b/test/test_unicode_font/unicodeFontTests.cpp @@ -0,0 +1,149 @@ + +#include +#include +#include +#include +#include +#include +#include + +class UnitTestPlotter : public TextPlotPipeline { +private: + std::deque 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() {} \ No newline at end of file diff --git a/test/test_utf8_processing/utf8Processing.cpp b/test/test_utf8_processing/utf8Processing.cpp new file mode 100644 index 0000000..7b3aea2 --- /dev/null +++ b/test/test_utf8_processing/utf8Processing.cpp @@ -0,0 +1,220 @@ +// with thanks to https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt for many of the ideas in this test. + +#include +#include +#include +#include + +std::deque unicodeChars; +const size_t max_size = 10; + +void setUp() { + unicodeChars.clear(); +} + +void tearDown() {} + +void textHandler(void* handler, uint32_t charCode) { + if (unicodeChars.size() == max_size) { + unicodeChars.pop_front(); + } + unicodeChars.push_back(charCode); +} + +uint32_t getFromBufferOrError() { + if (!unicodeChars.empty()) { + uint32_t charCode = unicodeChars.front(); + unicodeChars.pop_front(); + return charCode; + } else { + return TC_UNICODE_CHAR_ERROR; + } +} + +void test_Utf8EncoderAscii() { + tccore::Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_EXT_ASCII); + textProcessor.pushChars("Hello"); + // force extended ascii processing! + textProcessor.pushChar((char)199); + textProcessor.pushChar((char)200); + + TEST_ASSERT_EQUAL_UINT32(72, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(101, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(108, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(108, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(111, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(199U, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(200U, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); +} + +void test_Utf8EncoderUnicodeCodesDirect() { + tccore::Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); + + textProcessor.pushChar((char)0b00100100); + TEST_ASSERT_EQUAL_UINT32(0x24, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); + + textProcessor.pushChar((char)0b11000010); + textProcessor.pushChar((char)0b10100011); + TEST_ASSERT_EQUAL_UINT32(0xA3, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); + + textProcessor.pushChar((char)0b11100010); + textProcessor.pushChar((char)0b10000010); + textProcessor.pushChar((char)0b10101100); + TEST_ASSERT_EQUAL_UINT32(0x20AC, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); +} + +void test_Utf8EncoderUnicodeBasicCase() { + tccore::Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); + textProcessor.pushChars("Hello"); + + TEST_ASSERT_EQUAL_UINT32(72, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(101, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(108, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(108, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(111, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); +} + +void test_Utf8EncoderUnicodeMulti() { + tccore::Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); + textProcessor.pushChars("Світ"); // \xD0\xA1\xD0\xB2\xD1\x96\xD1\x82 + + TEST_ASSERT_EQUAL_UINT32(0x0421, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(0x0432, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(0x0456, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(0x0442, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); +} + +void test_Utf8EncoderUnicodeOverlongNull() { + tccore::Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); + textProcessor.pushChar((char)0xc0); + textProcessor.pushChar((char)0x80); + TEST_ASSERT_EQUAL_UINT32(TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); + + textProcessor.reset(); + textProcessor.pushChar((char)0xe0); + textProcessor.pushChar((char)0x80); + textProcessor.pushChar((char)0x80); + TEST_ASSERT_EQUAL_UINT32(TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); + + textProcessor.reset(); + textProcessor.pushChar((char)0xf0); + textProcessor.pushChar((char)0x80); + textProcessor.pushChar((char)0x80); + textProcessor.pushChar((char)0x80); + TEST_ASSERT_EQUAL_UINT32(TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); +} + +void test_BrokenSequenceContinueAsAscii() { + tccore::Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); + textProcessor.pushChar((char)0xc3); + textProcessor.pushChar((char)'H'); + textProcessor.pushChar((char)'I'); + TEST_ASSERT_EQUAL_UINT32(TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32('H', getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32('I', getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); + + textProcessor.reset(); + textProcessor.pushChar((char)0xe1); + textProcessor.pushChar((char)0x84); + textProcessor.pushChar((char)'H'); + textProcessor.pushChar((char)'I'); + TEST_ASSERT_EQUAL_UINT32(TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32('H', getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32('I', getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); + + textProcessor.reset(); + textProcessor.pushChar((char)0xf1); + textProcessor.pushChar((char)0x82); + textProcessor.pushChar((char)0x81); + textProcessor.pushChar((char)'A'); + TEST_ASSERT_EQUAL_UINT32(TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32('A', getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); + + textProcessor.reset(); + textProcessor.pushChar((char)0xf1); + textProcessor.pushChars("С"); // Cyrillic letter + TEST_ASSERT_EQUAL_UINT32(TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(0x0421, getFromBufferOrError()); + + textProcessor.reset(); + textProcessor.pushChar((char)0xf1); + textProcessor.pushChar((char)0x81); + textProcessor.pushChars("С"); // Cyrillic letter + TEST_ASSERT_EQUAL_UINT32(TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(0x0421, getFromBufferOrError()); +} + +void test_invalidBytesNotProcessed() { + tccore::Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); + textProcessor.pushChar((char)0xff); + TEST_ASSERT_EQUAL_UINT32(TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); + + textProcessor.reset(); + textProcessor.pushChar((char)0xfe); + TEST_ASSERT_EQUAL_UINT32(TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); +} + +void test_Utf8EncoderUnicodeOverlongSlash() { + tccore::Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); + textProcessor.pushChar((char)0xc0); + textProcessor.pushChar((char)0xAF); + TEST_ASSERT_EQUAL_UINT32(TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); + + textProcessor.reset(); + textProcessor.pushChar((char)0xe0); + textProcessor.pushChar((char)0x80); + textProcessor.pushChar((char)0xAF); + TEST_ASSERT_EQUAL_UINT32(TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); + + textProcessor.reset(); + textProcessor.pushChar((char)0xf0); + textProcessor.pushChar((char)0x80); + textProcessor.pushChar((char)0x80); + textProcessor.pushChar((char)0xAF); + TEST_ASSERT_EQUAL_UINT32(TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); +} + +void test_Utf8EncoderReallyLargeCodes() { + tccore::Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); + textProcessor.pushChars("ﬓﬔﬕﬖ"); + // force extended ascii processing! + + TEST_ASSERT_EQUAL_UINT32(0xFB13, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(0xFB14, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(0xFB15, getFromBufferOrError()); + TEST_ASSERT_EQUAL_UINT32(0xFB16, getFromBufferOrError()); + TEST_ASSERT_FALSE(!unicodeChars.empty()); +} + +void setup() { + UNITY_BEGIN(); + RUN_TEST(test_Utf8EncoderAscii); + RUN_TEST(test_Utf8EncoderUnicodeCodesDirect); + RUN_TEST(test_Utf8EncoderUnicodeBasicCase); + RUN_TEST(test_Utf8EncoderUnicodeMulti); + RUN_TEST(test_Utf8EncoderUnicodeOverlongNull); + RUN_TEST(test_BrokenSequenceContinueAsAscii); + RUN_TEST(test_invalidBytesNotProcessed); + RUN_TEST(test_Utf8EncoderUnicodeOverlongSlash); + RUN_TEST(test_Utf8EncoderReallyLargeCodes); + UNITY_END(); +} + +void loop() {} \ No newline at end of file diff --git a/tests/utf8Processing/unicodeFontTests.cpp b/tests/utf8Processing/unicodeFontTests.cpp deleted file mode 100644 index b570179..0000000 --- a/tests/utf8Processing/unicodeFontTests.cpp +++ /dev/null @@ -1,138 +0,0 @@ - -#include -#include -#include -#include -#include -#include - -class UnitTestPlotter : public TextPlotPipeline { -private: - GenericCircularBuffer pixelsDrawn; - uint32_t col = 0; - Coord where = {0,0}; -public: - UnitTestPlotter(): pixelsDrawn(32) {} - ~UnitTestPlotter() =default; - - void drawPixel(uint16_t x, uint16_t y, uint32_t color) override { - pixelsDrawn.put(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() { - while(pixelsDrawn.available()) pixelsDrawn.get(); - where = {0,0}; - } -} unitTestPlotter; - -class TextUtilsFixture : public SimpleTest::UnitTestExecutor { -protected: - UnicodeFontHandler* handler = nullptr; -public: - void setup() override { - unitTestPlotter.init(); - handler = new UnicodeFontHandler(&unitTestPlotter, ENCMODE_UTF8); - handler->setFont(OpenSansCyrillicLatin18); - handler->setDrawColor(20); - } - - void teardown() override { - 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; - } - - } -}; - -testF(TextUtilsFixture, testTextExtents) { - int bl; - handler->setFont(OpenSansCyrillicLatin18); - Coord coord = handler->textExtents("Abc", &bl, false); - assertEquals((int16_t)31, coord.x); - assertEquals((int16_t)24, coord.y); - - handler->setFont(&RobotoMedium24); - coord = handler->textExtents("Abc", &bl, false); - assertEquals((int16_t)43, coord.x); - assertEquals((int16_t)28, coord.y); -} - -testF(TextUtilsFixture, testGetGlyphOnEachRange) { - assertTrue(checkGlyph(65, 320, 11, 13, 11, 0, -18)); - assertTrue(checkGlyph(55 + 128, 198, 2, 2, 5, 1, -12)); - assertTrue(checkGlyph(17 + 1024, 285, 8, 13, 11, 2, -18)); -} - -testF(TextUtilsFixture, testReadingEveryGlyphInRange) { - GlyphWithBitmap glyphWithBitmap; - - // test all known characters work - for(int i=32;i<127;i++) { - serlogF2(SER_DEBUG, "Test character = ", i); - assertTrue(handler->findCharInFont(i, glyphWithBitmap)); - assertNotEquals(nullptr, glyphWithBitmap.getGlyph()); - assertNotEquals(nullptr, glyphWithBitmap.getBitmapData()); - } - - // test a few that should fail - assertFalse(handler->findCharInFont(0, glyphWithBitmap)); - assertFalse(handler->findCharInFont(5, glyphWithBitmap)); - assertFalse(handler->findCharInFont(10, glyphWithBitmap)); - assertFalse(handler->findCharInFont(0xFFFF, glyphWithBitmap)); -} - -testF(TextUtilsFixture, testAdafruitFont) { - GlyphWithBitmap glyphWithBitmap; - handler->setFont(&RobotoMedium24); - - // test a few that should fail - assertTrue(checkGlyph(32, 0, 0, 0, 6, 0, -28)); - assertTrue(checkGlyph(48, 243, 12, 17, 14, 1, -23)); - assertTrue(checkGlyph(65, 611, 16, 17, 16, 0, -23)); - assertTrue(checkGlyph(126, 1990, 14, 5, 16, 1, -16)); - assertFalse(handler->findCharInFont(5, glyphWithBitmap)); - assertFalse(handler->findCharInFont(127, glyphWithBitmap)); - assertFalse(handler->findCharInFont(31, glyphWithBitmap)); - -} \ No newline at end of file diff --git a/tests/utf8Processing/utf8Processing.cpp b/tests/utf8Processing/utf8Processing.cpp deleted file mode 100644 index 75ee7c7..0000000 --- a/tests/utf8Processing/utf8Processing.cpp +++ /dev/null @@ -1,222 +0,0 @@ -// with thanks to https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt for many of the ideas in this test. - -#include -#include -#include -#include - -using namespace SimpleTest; -using namespace tccore; - -IOLOG_MBED_PORT_IF_NEEDED(USBTX, USBRX) - -void setup() { - IOLOG_START_SERIAL - startTesting(); -} - -DEFAULT_TEST_RUNLOOP - -GenericCircularBuffer unicodeChars(10); - -void textHandler(void* handler, uint32_t charCode) { - unicodeChars.put(charCode); -} - -uint32_t getFromBufferOrError() { - if(unicodeChars.available()) { - return unicodeChars.get(); - } else { - return TC_UNICODE_CHAR_ERROR; - } -} - -test(testUtf8EncoderAscii) { - while(unicodeChars.available()) unicodeChars.get(); - - Utf8TextProcessor textProcessor(textHandler, nullptr, ENCMODE_EXT_ASCII); - textProcessor.pushChars("Hello"); - // force extended ascii processing! - textProcessor.pushChar((char)199); - textProcessor.pushChar((char)200); - - assertEquals((uint32_t)72, getFromBufferOrError()); - assertEquals((uint32_t)101, getFromBufferOrError()); - assertEquals((uint32_t)108, getFromBufferOrError()); - assertEquals((uint32_t)108, getFromBufferOrError()); - assertEquals((uint32_t)111, getFromBufferOrError()); - assertEquals((uint32_t)199U, getFromBufferOrError()); - assertEquals((uint32_t)200U, getFromBufferOrError()); - assertFalse(unicodeChars.available()); -} - -test(testUtf8EncoderUnicodeCodesDirect) { - while(unicodeChars.available()) unicodeChars.get(); - Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); - - textProcessor.pushChar((char)0b00100100); - assertEquals((uint32_t)0x24, getFromBufferOrError()); - assertFalse(unicodeChars.available()); - - textProcessor.pushChar((char)0b11000010); - textProcessor.pushChar((char)0b10100011); - assertEquals((uint32_t)0xA3, getFromBufferOrError()); - assertFalse(unicodeChars.available()); - - textProcessor.pushChar((char)0b11100010); - textProcessor.pushChar((char)0b10000010); - textProcessor.pushChar((char)0b10101100); - assertEquals((uint32_t)0x20AC, getFromBufferOrError()); - assertFalse(unicodeChars.available()); -} - - -test(testUtf8EncoderUnicodeBasicCase) { - while(unicodeChars.available()) unicodeChars.get(); - - Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); - textProcessor.pushChars("Hello"); - - assertEquals((uint32_t)72, getFromBufferOrError()); - assertEquals((uint32_t)101, getFromBufferOrError()); - assertEquals((uint32_t)108, getFromBufferOrError()); - assertEquals((uint32_t)108, getFromBufferOrError()); - assertEquals((uint32_t)111, getFromBufferOrError()); - assertFalse(unicodeChars.available()); -} - -test(testUtf8EncoderUnicodeMulti) { - while(unicodeChars.available()) unicodeChars.get(); - - Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); - textProcessor.pushChars("Світ");// \xD0\xA1\xD0\xB2\xD1\x96\xD1\x82 - - assertEquals((uint32_t)0x0421, getFromBufferOrError()); - assertEquals((uint32_t)0x0432, getFromBufferOrError()); - assertEquals((uint32_t)0x0456, getFromBufferOrError()); - assertEquals((uint32_t)0x0442, getFromBufferOrError()); - assertFalse(unicodeChars.available()); -} - -test(testUtf8EncoderUnicodeOverlongNull) { - while(unicodeChars.available()) unicodeChars.get(); - - Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); - textProcessor.pushChar((char)0xc0); - textProcessor.pushChar((char)0x80); - assertEquals((uint32_t)TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); - assertFalse(unicodeChars.available()); - - textProcessor.reset(); - textProcessor.pushChar((char)0xe0); - textProcessor.pushChar((char)0x80); - textProcessor.pushChar((char)0x80); - assertEquals((uint32_t)TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); - assertFalse(unicodeChars.available()); - - textProcessor.reset(); - textProcessor.pushChar((char)0xf0); - textProcessor.pushChar((char)0x80); - textProcessor.pushChar((char)0x80); - textProcessor.pushChar((char)0x80); - assertEquals((uint32_t)TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); - assertFalse(unicodeChars.available()); -} - -test(testBrokenSequenceContinueAsAscii) { - while(unicodeChars.available()) unicodeChars.get(); - - Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); - textProcessor.pushChar((char)0xc3); - textProcessor.pushChar((char)'H'); - textProcessor.pushChar((char)'I'); - assertEquals((uint32_t)TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); - assertEquals((uint32_t)'H', getFromBufferOrError()); - assertEquals((uint32_t)'I', getFromBufferOrError()); - assertFalse(unicodeChars.available()); - - textProcessor.reset(); - textProcessor.pushChar((char)0xe1); - textProcessor.pushChar((char)0x84); - textProcessor.pushChar((char)'H'); - textProcessor.pushChar((char)'I'); - assertEquals((uint32_t)TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); - assertEquals((uint32_t)'H', getFromBufferOrError()); - assertEquals((uint32_t)'I', getFromBufferOrError()); - assertFalse(unicodeChars.available()); - - textProcessor.reset(); - textProcessor.pushChar((char)0xf1); - textProcessor.pushChar((char)0x82); - textProcessor.pushChar((char)0x81); - textProcessor.pushChar((char)'A'); - assertEquals((uint32_t)TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); - assertEquals((uint32_t)'A', getFromBufferOrError()); - assertFalse(unicodeChars.available()); - - textProcessor.reset(); - textProcessor.pushChar((char)0xf1); - textProcessor.pushChars("С");// Cyrillic letter - assertEquals((uint32_t)TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); - assertEquals((uint32_t)0x0421, getFromBufferOrError()); - - textProcessor.reset(); - textProcessor.pushChar((char)0xf1); - textProcessor.pushChar((char)0x81); - textProcessor.pushChars("С");// Cyrillic letter - assertEquals((uint32_t)TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); - assertEquals((uint32_t)0x0421, getFromBufferOrError()); -} - -test(invalidBytesNotProcessed) { - while(unicodeChars.available()) unicodeChars.get(); - - Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); - textProcessor.pushChar((char)0xff); - assertEquals((uint32_t)TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); - assertFalse(unicodeChars.available()); - - textProcessor.reset(); - textProcessor.pushChar((char)0xfe); - assertEquals((uint32_t)TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); - assertFalse(unicodeChars.available()); -} - -test(testUtf8EncoderUnicodeOverlongSlash) { - while(unicodeChars.available()) unicodeChars.get(); - - Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); - textProcessor.pushChar((char)0xc0); - textProcessor.pushChar((char)0xAF); - assertEquals((uint32_t)TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); - assertFalse(unicodeChars.available()); - - textProcessor.reset(); - textProcessor.pushChar((char)0xe0); - textProcessor.pushChar((char)0x80); - textProcessor.pushChar((char)0xAF); - assertEquals((uint32_t)TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); - assertFalse(unicodeChars.available()); - - textProcessor.reset(); - textProcessor.pushChar((char)0xf0); - textProcessor.pushChar((char)0x80); - textProcessor.pushChar((char)0x80); - textProcessor.pushChar((char)0xAF); - assertEquals((uint32_t)TC_UNICODE_CHAR_ERROR, getFromBufferOrError()); - assertFalse(unicodeChars.available()); -} - -test(testUtf8EncoderReallyLargeCodes) { - while(unicodeChars.available()) unicodeChars.get(); - - Utf8TextProcessor textProcessor(textHandler, nullptr, tccore::ENCMODE_UTF8); - textProcessor.pushChars("ﬓﬔﬕﬖ"); - // force extended ascii processing! - - assertEquals((uint32_t)0xFB13, getFromBufferOrError()); - assertEquals((uint32_t)0xFB14, getFromBufferOrError()); - assertEquals((uint32_t)0xFB15, getFromBufferOrError()); - assertEquals((uint32_t)0xFB16, getFromBufferOrError()); - assertFalse(unicodeChars.available()); -} \ No newline at end of file