diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 917e74a0d..010528494 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -24,6 +24,8 @@ jobs: run: | cd unit-tests/lib_standard_app/ cmake -Bbuild -H. && make -C build && make -C build test + cd ../lib_nbgl/ + cmake -Bbuild -H. && make -C build && CTEST_OUTPUT_ON_FAILURE=1 make -C build test - name: Generate code coverage run: | @@ -33,17 +35,24 @@ jobs: lcov --directory . -b "$(realpath build/)" --add-tracefile coverage.base --add-tracefile coverage.capture -o coverage.info && \ lcov --directory . -b "$(realpath build/)" --remove coverage.info '*/unit-tests/*' -o coverage.info && \ genhtml coverage.info -o coverage + cd ../lib_nbgl/ + lcov --directory . -b "$(realpath build/)" --capture --initial -o coverage.base && \ + lcov --rc lcov_branch_coverage=1 --directory . -b "$(realpath build/)" --capture -o coverage.capture && \ + lcov --directory . -b "$(realpath build/)" --add-tracefile coverage.base --add-tracefile coverage.capture -o coverage.info && \ + lcov --directory . -b "$(realpath build/)" --remove coverage.info '*/unit-tests/*' -o coverage.info && \ + genhtml coverage.info -o coverage + - uses: actions/upload-artifact@v3 with: name: code-coverage - path: unit-tests/lib_standard_app/coverage + path: unit-tests/lib_*/coverage - name: Upload to codecov.io uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} - files: ./unit-tests/lib_standard_app/coverage.info + files: ./unit-tests/lib_standard_app/coverage.info,./unit-tests/lib_nbgl/coverage.info flags: unittests name: codecov-app-boilerplate fail_ci_if_error: true diff --git a/.gitignore b/.gitignore index b1239b0ea..853878550 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,4 @@ lib_bagl/src/bagl_font_open_sans_light_16px_unicode.inc lib_bagl/src/bagl_font_open_sans_regular_11px_unicode.inc lib_nbgl/build TAGS -unit-tests/lib_standard_app/build/ +unit-tests/lib_*/build/ diff --git a/lib_nbgl/src/nbgl_fonts.c b/lib_nbgl/src/nbgl_fonts.c index 7a0c007bf..8bc90238c 100644 --- a/lib_nbgl/src/nbgl_fonts.c +++ b/lib_nbgl/src/nbgl_fonts.c @@ -734,7 +734,7 @@ nbgl_unicode_ctx_t* nbgl_getUnicodeFont(nbgl_font_id_e fontId) { */ const nbgl_font_unicode_character_t *nbgl_getUnicodeFontCharacter(uint32_t unicode) { #if defined(HAVE_LANGUAGE_PACK) - const nbgl_font_unicode_character_t *characters = unicodeCtx.characters; + const nbgl_font_unicode_character_t *characters = (const nbgl_font_unicode_character_t *)PIC(unicodeCtx.characters); uint32_t n = language_pack->nb_characters; if (characters == NULL) { return NULL; @@ -742,11 +742,11 @@ const nbgl_font_unicode_character_t *nbgl_getUnicodeFontCharacter(uint32_t unico // For the moment, let just parse the full array, but at the end let use // binary search as data are sorted by unicode value ! for (unsigned i=0; i < n-1; i++, characters++) { - if ((PIC(characters))->char_unicode == unicode) { + if (characters->char_unicode == unicode) { // Compute & store the number of bytes used to display this character unicodeCtx.unicode_character_byte_count = \ - (PIC(characters+1))->bitmap_offset - (PIC(characters))->bitmap_offset; - return (PIC(characters)); + (characters+1)->bitmap_offset - characters->bitmap_offset; + return characters; } } // By default, let's use the last Unicode character, which should be the @@ -754,8 +754,8 @@ const nbgl_font_unicode_character_t *nbgl_getUnicodeFontCharacter(uint32_t unico // Compute & store the number of bytes used to display this character unicodeCtx.unicode_character_byte_count = unicodeCtx.font->bitmap_len - \ - (PIC(characters))->bitmap_offset; - return (PIC(characters)); + characters->bitmap_offset; + return characters; #else //defined(HAVE_LANGUAGE_PACK) UNUSED(unicode); // id not found diff --git a/unit-tests/lib_nbgl/CMakeLists.txt b/unit-tests/lib_nbgl/CMakeLists.txt new file mode 100644 index 000000000..bd86247ea --- /dev/null +++ b/unit-tests/lib_nbgl/CMakeLists.txt @@ -0,0 +1,62 @@ +cmake_minimum_required(VERSION 3.10) + +if(${CMAKE_VERSION} VERSION_LESS 3.10) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +endif() + +# project information +project(unit_tests + VERSION 0.1 + DESCRIPTION "Unit tests for NBGL" + LANGUAGES C) + + +# guard against bad build-type strings +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Debug") +endif() + +include(CTest) +ENABLE_TESTING() + +# specify C standard +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED True) +add_compile_definitions(HAVE_BAGL_FONT_INTER_REGULAR_24PX) +add_compile_definitions(HAVE_BAGL_FONT_INTER_SEMIBOLD_24PX) +add_compile_definitions(HAVE_BAGL_FONT_INTER_MEDIUM_32PX) +add_compile_definitions(HAVE_BAGL_FONT_HMALPHAMONO_MEDIUM_32PX) +add_compile_definitions(HAVE_LANGUAGE_PACK) +add_compile_definitions(HAVE_UNICODE_SUPPORT) +add_compile_definitions(USB_SEGMENT_SIZE=64) + +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall ${DEFINES} -g -O0 --coverage") + +set(GCC_COVERAGE_LINK_FLAGS "--coverage -lgcov") +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}") + +# guard against in-source builds +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) + message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt. ") +endif() + +add_compile_definitions(TEST) + +include_directories(../../target/stax/include) +include_directories(../../include) +include_directories(../../lib_nbgl/include) +include_directories(../../lib_ux_stax) + +add_executable(test_nbgl_fonts test_nbgl_fonts.c) +add_executable(test_nbgl_screen test_nbgl_screen.c) + +add_library(nbgl_obj_pool SHARED ../../lib_nbgl/src/nbgl_obj_pool.c) +add_library(nbgl_fonts SHARED ../../lib_nbgl/src/nbgl_fonts.c) +add_library(nbgl_screen SHARED ../../lib_nbgl/src/nbgl_screen.c) + +target_link_libraries(test_nbgl_fonts PUBLIC cmocka gcov nbgl_fonts) +target_link_libraries(test_nbgl_screen PUBLIC cmocka gcov nbgl_screen nbgl_obj_pool) + +add_test(test_nbgl_fonts test_nbgl_fonts) +add_test(test_nbgl_screen test_nbgl_screen) diff --git a/unit-tests/lib_nbgl/README.md b/unit-tests/lib_nbgl/README.md new file mode 100644 index 000000000..f41d4dd25 --- /dev/null +++ b/unit-tests/lib_nbgl/README.md @@ -0,0 +1,36 @@ +# Unit tests + +## Prerequisite + +Be sure to have installed: + +- CMake >= 3.10 +- CMocka >= 1.1.5 + +and for code coverage generation: + +- lcov >= 1.14 + +## Overview + +In `unit-tests` folder, compile with + +``` +cmake -Bbuild -H. && make -C build +``` + +and run tests with + +``` +CTEST_OUTPUT_ON_FAILURE=1 make -C build test +``` + +## Generate code coverage + +Just execute in `unit-tests` folder + +``` +./gen_coverage.sh +``` + +it will output `coverage.total` and `coverage/` folder with HTML details (in `coverage/index.html`). diff --git a/unit-tests/lib_nbgl/bolos_pack_fr.bin b/unit-tests/lib_nbgl/bolos_pack_fr.bin new file mode 100644 index 000000000..b3cab90c7 Binary files /dev/null and b/unit-tests/lib_nbgl/bolos_pack_fr.bin differ diff --git a/unit-tests/lib_nbgl/test_nbgl_fonts.c b/unit-tests/lib_nbgl/test_nbgl_fonts.c new file mode 100644 index 000000000..bc06091b0 --- /dev/null +++ b/unit-tests/lib_nbgl/test_nbgl_fonts.c @@ -0,0 +1,167 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include "nbgl_fonts.h" +#include "ux_loc.h" + +const LANGUAGE_PACK *language_pack = NULL; + +static void fetch_language_packs(void) { + // If we are looking for a language pack: + // - if the expected language is found then we'll use its begin/length range. + // - else we'll use the built-in package and need to reset allowed MMU range. + + FILE *fptr = NULL; + + fptr = fopen("../bolos_pack_fr.bin","rb"); + + assert_non_null(fptr); + if (fptr != NULL) { + fseek(fptr, 0, SEEK_END); + + uint32_t len = ftell(fptr); + + fseek(fptr, 0, SEEK_SET); + + uint8_t *source = (uint8_t*)malloc(len); + + assert_non_null(source); + + assert_int_equal(fread((unsigned char*)source, 1, len, fptr),len); + + fclose(fptr); + + language_pack = (LANGUAGE_PACK *)source; + + } +} + +#include "nbgl_fonts.h" +#include "nbgl_font_hmalpha_mono_medium_32.inc" +#include "nbgl_font_inter_regular_24.inc" +#include "nbgl_font_inter_semibold_24.inc" +#include "nbgl_font_inter_medium_32.inc" +#include "nbgl_font_inter_regular_24_1bpp.inc" +#include "nbgl_font_inter_semibold_24_1bpp.inc" +#include "nbgl_font_inter_medium_32_1bpp.inc" + +static const nbgl_font_t* const C_nbgl_fonts[] = { +#include "nbgl_font_rom_struct.inc" +}; +static const unsigned int C_nbgl_fonts_count = sizeof(C_nbgl_fonts)/sizeof(C_nbgl_fonts[0]); + +const nbgl_font_t* nbgl_font_getFont(unsigned int fontId) { + unsigned int i = C_nbgl_fonts_count; + while(i--) { + // font id match this entry (non indexed array) + if (C_nbgl_fonts[i]->font_id == fontId) { + return C_nbgl_fonts[i]; + } + } + + // id not found + return NULL; +} + +void *pic(void *addr) { + return addr; +} + +static void test_get_length(void **state __attribute__((unused))) { + char *str_with_unicode= "çoto"; + char *str_without_unicode= "toto"; + fetch_language_packs(); + + uint16_t width = nbgl_getTextWidth(BAGL_FONT_INTER_REGULAR_24px, str_without_unicode); + assert_int_equal(width,46); + uint16_t len = nbgl_getTextLength(str_without_unicode); + + assert_int_equal(len,4); + len = nbgl_getTextLength(str_with_unicode); + assert_int_equal(len,5); + assert_int_equal(strlen(str_with_unicode),5); + + width = nbgl_getTextWidth(BAGL_FONT_INTER_REGULAR_24px, str_with_unicode); + assert_int_equal(width,45); + + char myChar = 0x30; + width = nbgl_getCharWidth(BAGL_FONT_INTER_REGULAR_24px, &myChar); + assert_int_equal(width,15); + width = nbgl_getCharWidth(BAGL_FONT_INTER_REGULAR_24px, "ç"); + assert_int_equal(width,8); + + assert_int_equal(nbgl_getTextNbLines(str_without_unicode),1); + assert_int_equal(nbgl_getTextNbLines(str_with_unicode),1); + assert_int_equal(nbgl_getTextNbLines("bonjour\nau revoir"),2); + assert_int_equal(nbgl_getTextNbLines("bonjour\nau çevoir"),2); + + // '\n' is considered as end of string for nbgl_getTextLength + assert_int_equal(nbgl_getTextLength("bonjour\nau revoir"),7); + // 'ç' counts for 2 bytes + assert_int_equal(nbgl_getTextLength("bonçour\nau revoir"),8); + + nbgl_getTextMaxLenAndWidth(BAGL_FONT_INTER_REGULAR_24px,"totoour\nau revoir", 50, &len, &width, false); + assert_int_equal(len,4); + assert_int_equal(width,46); + + assert_int_equal(nbgl_getTextWidth(BAGL_FONT_INTER_REGULAR_24px,"au revoir"),100); + assert_int_equal(nbgl_getTextWidth(BAGL_FONT_INTER_REGULAR_24px,"totoour"),83); + assert_int_equal(strlen("totoour\nau revoir"),17); + nbgl_getTextMaxLenAndWidth(BAGL_FONT_INTER_REGULAR_24px,"totoour\nau revoir", 100, &len, &width, false); + assert_int_equal(len,7); + assert_int_equal(width,83); // width of latest line, doesn't mean anything + + uint8_t nbLines = nbgl_getTextNbLinesInWidth(BAGL_FONT_INTER_REGULAR_24px,"totoour\nau revoir", 100, false); + assert_int_equal(nbLines,2); + nbLines = nbgl_getTextNbLinesInWidth(BAGL_FONT_INTER_REGULAR_24px,"totoour\nau revoir", 60, false); + assert_int_equal(nbLines,4); + nbLines = nbgl_getTextNbLinesInWidth(BAGL_FONT_INTER_REGULAR_24px,"totoour\na", 50, false); + assert_int_equal(nbLines,3); + nbLines = nbgl_getTextNbLinesInWidth(BAGL_FONT_INTER_REGULAR_24px,"totoour\nau revoi", 50, false); + assert_int_equal(nbLines,4); + + nbLines = nbgl_getTextNbLinesInWidth(BAGL_FONT_INTER_REGULAR_24px,"totçour\nau revoir", 100, false); + assert_int_equal(nbLines,2); + nbLines = nbgl_getTextNbLinesInWidth(BAGL_FONT_INTER_REGULAR_24px,"totçour\nau revoir", 60, false); + assert_int_equal(nbLines,4); + nbLines = nbgl_getTextNbLinesInWidth(BAGL_FONT_INTER_REGULAR_24px,"totçour\na", 50, false); + assert_int_equal(nbLines,3); + nbLines = nbgl_getTextNbLinesInWidth(BAGL_FONT_INTER_REGULAR_24px,"totçour\nau revoi", 50, false); + assert_int_equal(nbLines,4); + + nbLines = nbgl_getTextNbLinesInWidth(BAGL_FONT_INTER_REGULAR_24px,"totçour au revoi", 100, true); + assert_int_equal(nbLines,2); + + nbgl_getTextMaxLenAndWidth(BAGL_FONT_INTER_REGULAR_24px,"totçour\nau revoir", 50, &len, &width, false); + assert_int_equal(len,5); + assert_int_equal(width,40); + + char textToWrap[32] = "toto"; + nbgl_textWrapOnNbLines(BAGL_FONT_INTER_SEMIBOLD_24px, textToWrap, 156, 2); + assert_string_equal(textToWrap,"toto"); + + strcpy(textToWrap,"bonjour tu aimes les mois"); + nbgl_textWrapOnNbLines(BAGL_FONT_INTER_SEMIBOLD_24px, textToWrap, 156, 2); + assert_string_equal(textToWrap,"bonjour tu\naimes les..."); + + strcpy(textToWrap,"bonjourtuaimestr les mois"); + nbgl_textWrapOnNbLines(BAGL_FONT_INTER_SEMIBOLD_24px, textToWrap, 156, 2); + assert_string_equal(textToWrap,"bonjourtuaimestr les..."); + + nbLines = nbgl_getTextNbLinesInWidth(BAGL_FONT_INTER_MEDIUM_32px, "AB WWWWWWWW WWW W", 200, true); + assert_int_equal(nbLines,4); + + int height = nbgl_getTextHeightInWidth(BAGL_FONT_INTER_MEDIUM_32px, "AB WWWWWWWW WWW W", 200, true); + assert_int_equal(height,160); +} + +int main(int argc, char **argv) { + const struct CMUnitTest tests[] = {cmocka_unit_test(test_get_length)}; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/unit-tests/lib_nbgl/test_nbgl_screen.c b/unit-tests/lib_nbgl/test_nbgl_screen.c new file mode 100644 index 000000000..5d72e81e3 --- /dev/null +++ b/unit-tests/lib_nbgl/test_nbgl_screen.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include "nbgl_screen.h" +#include "nbgl_debug.h" +#include "ux_loc.h" + +#define UNUSED(x) (void)x + +unsigned long gLogger = 0 +// | (1<container.nbChildren, 1); + assert_ptr_equal(screen->container.children, nbgl_screenGetElements(screen1)); + + // push a third screen and assert its index is 2, and that it is on top of stack + screen2 = nbgl_screenPush(&elements2, 2, NULL, NULL); + assert_int_equal(screen2, 2); + screen = (nbgl_screen_t *)nbgl_screenGetTop(); + assert_int_equal(screen->container.nbChildren, 2); + assert_ptr_equal(screen->container.children, nbgl_screenGetElements(screen2)); + + // push a fourth screen and assert its index is 3, and that it is on top of stack + screen3 = nbgl_screenPush(&elements3, 3, NULL, NULL); + assert_int_equal(screen3, 3); + screen = (nbgl_screen_t *)nbgl_screenGetTop(); + assert_int_equal(screen->container.nbChildren, 3); + assert_ptr_equal(screen->container.children, nbgl_screenGetElements(screen3)); + + // pop the fourth screen and assert the third one is on top of stack + nbgl_screenPop(screen3); + screen = (nbgl_screen_t *)nbgl_screenGetTop(); + assert_int_equal(screen->container.nbChildren, 2); + assert_ptr_equal(screen->container.children, nbgl_screenGetElements(screen2)); + + // pop the second screen and assert the third one is on top of stack + nbgl_screenPop(screen1); + screen = (nbgl_screen_t *)nbgl_screenGetTop(); + assert_int_equal(screen->container.nbChildren, 2); + assert_ptr_equal(screen->container.children, nbgl_screenGetElements(screen2)); + + // push a screen and assert its index is 1 (first unused index) + screen3 = nbgl_screenPush(&elements3, 3, NULL, NULL); + assert_int_equal(screen3, 1); +} + +int main(int argc, char **argv) { + const struct CMUnitTest tests[] = {cmocka_unit_test(test_push_pop)}; + return cmocka_run_group_tests(tests, NULL, NULL); +}