First commit with working display

This commit is contained in:
Ricardo LAP
2026-09-13 20:50:51 +00:00
parent a83af259b3
commit c54c68c899
6690 changed files with 1093387 additions and 2 deletions

View File

@@ -0,0 +1,9 @@
*.out
*_Runner.c
*.bin
*.dat
*.raw
*_err.png
build_*/
report*/
wayland_protocols/

View File

@@ -0,0 +1,572 @@
if(ESP_PLATFORM)
###################################
# Tests do not build for ESP-IDF. #
###################################
else()
cmake_minimum_required(VERSION 3.16)
if(WIN32)
set(VCPKG_TARGET_TRIPLET "x64-windows")
include($ENV{VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake)
endif()
project(lvgl_tests LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_C_STANDARD 99)
set(FLAG_CHECK_WHITELIST --coverage -fsanitize=address -Werror)
include(CheckCCompilerFlag)
include(CheckCXXCompilerFlag)
function(filter_compiler_options lang options_out)
set(options ${ARGN})
foreach(option ${options})
if (option IN_LIST FLAG_CHECK_WHITELIST)
list(APPEND ${options_out} ${option})
continue()
endif ()
string(TOUPPER FLAG_SUPPORTED_FOR_${lang}_${option} option_var_name)
string(REPLACE "-" "_" option_var_name ${option_var_name})
string(REPLACE "+" "_" option_var_name ${option_var_name})
if (${lang} STREQUAL C)
check_c_compiler_flag(${option} ${option_var_name})
elseif (${lang} STREQUAL CXX)
check_cxx_compiler_flag(${option} ${option_var_name})
else()
message(FATAL_ERROR "Unknown language ${lang}")
endif ()
if(${option_var_name})
list(APPEND ${options_out} ${option})
endif()
endforeach()
set(${options_out} ${${options_out}} PARENT_SCOPE)
endfunction()
find_program(VALGRIND_EXECUTABLE valgrind)
if (VALGRIND_EXECUTABLE)
set(MEMORYCHECK_COMMAND ${VALGRIND_EXECUTABLE})
set(MEMORYCHECK_COMMAND_OPTIONS --error-exitcode=1)
endif()
include(CTest)
set(LVGL_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(SANITIZE_AND_COVERAGE_OPTIONS
-fsanitize=address
-fsanitize=leak
-fsanitize=undefined
--coverage
)
set(LVGL_TEST_OPTIONS_VG_LITE
-DLV_TEST_OPTION=6
-Wno-dangling-pointer # workaround for thorvg dangling-pointer warning
)
set(LVGL_TEST_OPTIONS_RISCV_V
-DLV_TEST_OPTION=5
-DLVGL_CI_USING_SYS_HEAP
-DLV_USE_DRAW_SW_ASM=LV_DRAW_SW_ASM_RISCV_V
)
set(LVGL_TEST_OPTIONS_SDL
-DLV_TEST_OPTION=7
)
set(LVGL_TEST_OPTIONS_MINIMAL_MONOCHROME
-DLV_TEST_OPTION=1
)
set(LVGL_TEST_OPTIONS_NORMAL_8BIT
-DLV_TEST_OPTION=2
)
set(LVGL_TEST_OPTIONS_16BIT
-DLV_TEST_OPTION=3
)
set(LVGL_TEST_OPTIONS_FULL_24BIT
-DLV_TEST_OPTION=4
)
set(LVGL_TEST_OPTIONS_FULL_32BIT
-DLV_TEST_OPTION=5
)
set(LVGL_TEST_OPTIONS_TEST_SYSHEAP
-DLV_TEST_OPTION=5
-DLVGL_CI_USING_SYS_HEAP
-Wno-unused-but-set-variable # unused variables are common in the dual-heap arrangement
${SANITIZE_AND_COVERAGE_OPTIONS}
)
set(LVGL_TEST_OPTIONS_TEST_DEFHEAP
-DLV_TEST_OPTION=5
-DLV_USE_OBJ_PROPERTY=1 # add obj property test and disable pedantic
-DLV_USE_OBJ_PROPERTY_NAME=1
-DLVGL_CI_USING_DEF_HEAP
${SANITIZE_AND_COVERAGE_OPTIONS}
)
if (OPTIONS_SDL)
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_SDL})
elseif (OPTIONS_NORMAL_8BIT)
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_NORMAL_8BIT})
elseif (OPTIONS_16BIT)
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_16BIT})
elseif (OPTIONS_24BIT)
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_24BIT})
elseif (OPTIONS_FULL_32BIT)
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_FULL_32BIT})
elseif (OPTIONS_TEST_SYSHEAP)
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_TEST_SYSHEAP})
filter_compiler_options (C TEST_LIBS ${SANITIZE_AND_COVERAGE_OPTIONS})
set (CONFIG_LV_BUILD_EXAMPLES OFF CACHE BOOL "disable examples" FORCE)
set (ENABLE_TESTS ON)
add_definitions(-DREF_IMGS_PATH="ref_imgs/")
elseif (OPTIONS_TEST_DEFHEAP)
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_TEST_DEFHEAP})
filter_compiler_options (C TEST_LIBS ${SANITIZE_AND_COVERAGE_OPTIONS})
set (CONFIG_LV_BUILD_EXAMPLES OFF CACHE BOOL "disable examples" FORCE)
set (ENABLE_TESTS ON)
add_definitions(-DREF_IMGS_PATH="ref_imgs/")
elseif (OPTIONS_TEST_MEMORYCHECK)
# sanitizer is disabled because valgrind uses LD_PRELOAD and the
# sanitizer lib needs to load first
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_TEST_SYSHEAP})
set (CONFIG_LV_BUILD_EXAMPLES OFF CACHE BOOL "disable examples" FORCE)
set (ENABLE_TESTS ON)
add_definitions(-DREF_IMGS_PATH="ref_imgs/")
elseif (OPTIONS_TEST_VG_LITE)
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_VG_LITE} -DLVGL_CI_USING_SYS_HEAP ${SANITIZE_AND_COVERAGE_OPTIONS})
filter_compiler_options (C TEST_LIBS ${SANITIZE_AND_COVERAGE_OPTIONS})
set (CONFIG_LV_BUILD_EXAMPLES OFF CACHE BOOL "disable examples" FORCE)
set (ENABLE_TESTS ON)
add_definitions(-DREF_IMGS_PATH="ref_imgs_vg_lite/")
# In 32-bit systems, the output of ThorVG's anti-aliasing algorithm has a slight deviation.
if ($ENV{NON_AMD64_BUILD})
# Set a tolerance value for the VG-Lite tests.
add_definitions(-DREF_IMG_TOLERANCE=9)
endif()
elseif (OPTIONS_TEST_RISCV_V)
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_RISCV_V} ${SANITIZE_AND_COVERAGE_OPTIONS})
filter_compiler_options (C TEST_LIBS ${SANITIZE_AND_COVERAGE_OPTIONS})
set (CONFIG_LV_BUILD_EXAMPLES OFF CACHE BOOL "disable examples" FORCE)
set (ENABLE_TESTS ON)
add_definitions(-DREF_IMGS_PATH="ref_imgs/")
message(STATUS "RISC-V Vector (RVV) software emulation test enabled")
else()
message(FATAL_ERROR "Must provide a known options value (check main.py?).")
endif()
if ($ENV{NON_AMD64_BUILD})
set(BUILD_TARGET_DEF -DNON_AMD64_BUILD)
message("Non AMD64 target is specified")
# Ensure 32-bit build
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -m32")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -m32")
endif()
if ($ENV{NON_AMD64_BUILD})
set(CMAKE_LIBRARY_PATH "/usr/lib/i386-linux-gnu" CACHE PATH "search 32bit lib path firstly")
set(ENV{PKG_CONFIG_PATH} "/usr/lib/i386-linux-gnu/pkgconfig:$ENV{PKG_CONFIG_PATH}")
endif()
# Options lvgl and examples are compiled with.
set(COMPILE_OPTIONS
${BUILD_OPTIONS}
${BUILD_TARGET_DEF}
)
if(NOT (CMAKE_C_COMPILER_ID STREQUAL "MSVC"))
list(APPEND COMPILE_OPTIONS
-pedantic-errors
-Wall
-Wformat-security
-Wclobbered
-Wdeprecated
-Wdouble-promotion
-Wempty-body
-Werror
-Wextra
-Wformat-security
-Wmaybe-uninitialized
-Wmissing-prototypes
-Wpointer-arith
-Wmultichar
-Wpedantic
-Wreturn-type
-Wshadow
-Wshift-negative-value
-Wsizeof-pointer-memaccess
#-Wstack-usage=6000
-Wtype-limits
-Wundef
-Wuninitialized
-Wunreachable-code
-Werror=float-conversion
-Werror=strict-aliasing
-Wno-double-promotion
-Wno-unreachable-code
-Wno-gnu-zero-variadic-macro-arguments
-Wunused-function
-Wno-overlength-strings
)
else()
list(APPEND COMPILE_OPTIONS
/W3 # enable all "production quality" warnings
/we4013 # treat function undefined as error
)
endif()
list(APPEND COMPILE_OPTIONS
${BUILD_OPTIONS}
${BUILD_TARGET_DEF}
)
# LV_USE_OBJ_PROPERTY is enabled in option OPTIONS_TEST_DEFHEAP
# It relies on C11 anonymous struct/union support, thus disable warnings.
if(OPTIONS_TEST_DEFHEAP)
list(REMOVE_ITEM COMPILE_OPTIONS -pedantic-errors -Wpedantic)
endif()
filter_compiler_options(C LVGL_C_COMPILE_OPTIONS ${COMPILE_OPTIONS})
if(NOT (CMAKE_C_COMPILER_ID STREQUAL "MSVC"))
# Options test cases are compiled with.
filter_compiler_options(C LVGL_TESTFILE_COMPILE_OPTIONS ${LVGL_C_COMPILE_OPTIONS} -Wno-missing-prototypes)
filter_compiler_options(CXX LVGL_CXX_COMPILE_OPTIONS
${COMPILE_OPTIONS}
-Wno-shadow
-Wno-unused-parameter
-Wno-c++11-extensions
-Wno-missing-prototypes
-Wno-deprecated-copy-with-user-provided-dtor
-Wno-float-conversion
-Wno-pedantic
)
else()
# Options test cases are compiled with.
filter_compiler_options(C LVGL_TESTFILE_COMPILE_OPTIONS ${LVGL_C_COMPILE_OPTIONS})
filter_compiler_options(CXX LVGL_CXX_COMPILE_OPTIONS
${COMPILE_OPTIONS}
)
endif()
get_filename_component(LVGL_DIR ${LVGL_TEST_DIR} DIRECTORY)
# Include lvgl project file.
# Set LV_BUILD_CONF_PATH so os_desktop.cmake knows about it before compiling lvgl
# Used to preprocess lv_conf_internal.h with the test config
set(LV_BUILD_CONF_PATH "${LVGL_TEST_DIR}/src/lv_test_conf.h" CACHE STRING "set test configuration")
include(${LVGL_DIR}/CMakeLists.txt)
target_compile_options(lvgl PUBLIC $<$<COMPILE_LANGUAGE:C>: ${LVGL_C_COMPILE_OPTIONS}>)
target_compile_options(lvgl PUBLIC $<$<COMPILE_LANGUAGE:CXX>:${LVGL_CXX_COMPILE_OPTIONS}>)
target_compile_definitions(lvgl PUBLIC LV_BUILD_TEST)
if(NOT (CMAKE_C_COMPILER_ID STREQUAL "MSVC"))
target_compile_options(lvgl PUBLIC $<$<COMPILE_LANGUAGE:ASM>:${LVGL_C_COMPILE_OPTIONS}>)
endif()
if (TARGET lvgl_thorvg)
target_compile_options(lvgl_thorvg PUBLIC $<$<COMPILE_LANGUAGE:C>:${LVGL_C_COMPILE_OPTIONS}>)
target_compile_options(lvgl_thorvg PUBLIC $<$<COMPILE_LANGUAGE:CXX>:${LVGL_CXX_COMPILE_OPTIONS}>)
endif()
if (TARGET lvgl_examples)
target_compile_options(lvgl_examples PUBLIC $<$<COMPILE_LANGUAGE:C>:${LVGL_C_COMPILE_OPTIONS}>)
target_compile_options(lvgl_examples PUBLIC $<$<COMPILE_LANGUAGE:CXX>:${LVGL_CXX_COMPILE_OPTIONS}>)
endif()
if (TARGET lvgl_demos)
target_compile_options(lvgl_demos PUBLIC $<$<COMPILE_LANGUAGE:C>:${LVGL_C_COMPILE_OPTIONS}>)
target_compile_options(lvgl_demos PUBLIC $<$<COMPILE_LANGUAGE:CXX>:${LVGL_CXX_COMPILE_OPTIONS}>)
endif()
# LVGL_CONF_DIR and LVGL_CONF_PATH are normalized and set by os_desktop.cmake
set(TEST_INCLUDE_DIRS
$<BUILD_INTERFACE:${LVGL_TEST_DIR}/src>
$<BUILD_INTERFACE:${LVGL_TEST_DIR}/unity>
$<BUILD_INTERFACE:${LVGL_TEST_DIR}>
$<BUILD_INTERFACE:${LVGL_CONF_DIR}>
)
file(GLOB_RECURSE TEST_IMAGES_SRC
${LVGL_TEST_DIR}/test_images/*.c
${LVGL_TEST_DIR}/../examples/assets/*.c)
add_library(test_common
STATIC
src/lv_test_init.c
src/test_assets/test_animimg001.c
src/test_assets/test_animimg002.c
src/test_assets/test_animimg003.c
src/test_assets/test_img_cogwheel_i4.c
src/test_assets/test_img_cogwheel_a8.c
src/test_assets/test_img_cogwheel_rgb565.c
src/test_assets/test_img_cogwheel_rgb565a8.c
src/test_assets/test_img_cogwheel_xrgb8888.c
src/test_assets/test_img_cogwheel_argb8888.c
src/test_assets/test_img_svg.c
src/test_assets/test_font_1.c
src/test_assets/test_font_2.c
src/test_assets/test_font_3.c
src/test_assets/test_font_montserrat_ascii_1bpp.c
src/test_assets/test_font_montserrat_ascii_2bpp.c
src/test_assets/test_font_montserrat_ascii_4bpp.c
src/test_assets/test_font_montserrat_ascii_8bpp.c
src/test_assets/test_font_montserrat_ascii_3bpp_compressed.c
src/test_assets/test_font_1_bin.c
src/test_assets/test_font_2_bin.c
src/test_assets/test_font_3_bin.c
src/test_assets/test_img_caret_down.c
src/test_assets/test_arc_bg.c
src/test_assets/test_img_bulb_gif.c
src/test_assets/test_img_lvgl_logo_png.c
src/test_assets/test_img_lvgl_logo_jpg.c
src/test_assets/test_img_emoji_F617.c
src/test_assets/test_ubuntu_font.c
src/test_assets/test_kern_one_otf.c
src/test_assets/test_imagebutton_left.c
src/test_assets/test_imagebutton_mid.c
src/test_assets/test_imagebutton_right.c
src/test_assets/test_music_button_play.c
src/test_assets/test_lottie_approve.c
unity/unity.c
${TEST_IMAGES_SRC}
)
target_include_directories(test_common PUBLIC ${TEST_INCLUDE_DIRS})
target_compile_options(test_common PUBLIC ${LVGL_TESTFILE_COMPILE_OPTIONS})
target_compile_definitions(test_common PRIVATE LV_BUILD_TEST ${LVGL_COMPILER_DEFINES})
# Generate one test executable for each source file pair.
# The sources in ${CMAKE_CURRENT_BINARY_DIR} is auto-generated, the
# sources in src/test_cases is the actual test case.
find_package(Ruby REQUIRED)
set(generate_test_runner_rb
${CMAKE_CURRENT_SOURCE_DIR}/unity/generate_test_runner.rb)
set(generate_test_runner_config ${CMAKE_CURRENT_SOURCE_DIR}/config.yml)
if(NOT WIN32)
# libjpeg is required for the jpeg test case
find_package(JPEG REQUIRED)
include_directories(${JPEG_INCLUDE_DIR})
find_package(PkgConfig)
pkg_check_modules(WebP libwebp)
if (WebP_FOUND)
include_directories(${WebP_INCLUDEDIR})
else()
message("WebP not found, defaulting to 0")
add_definitions(-DLV_USE_LIBWEBP=0)
endif()
# Wayland
find_package(PkgConfig)
pkg_check_modules(wayland_client wayland-client)
if (wayland_client_FOUND)
pkg_check_modules(WAYLAND REQUIRED wayland-cursor wayland-client wayland-egl xkbcommon)
link_libraries(${WAYLAND_LIBRARIES})
# Add auto generated source required for XDG shell
include_directories("${LVGL_TEST_DIR}/wayland_protocols")
target_sources(test_common PUBLIC "wayland_protocols/wayland_xdg_shell.c")
else()
add_definitions(-DLV_USE_WAYLAND=0)
endif()
endif()
# libpng is required for the png test case
find_package(PNG REQUIRED)
include_directories(${PNG_INCLUDE_DIR})
if(NOT WIN32)
# FFmpeg is optional for the image and video test cases
find_package(PkgConfig)
pkg_check_modules(FFMPEG IMPORTED_TARGET
libavformat
libavcodec
libswscale
libavutil
)
if(FFMPEG_FOUND)
include_directories(${FFMPEG_INCLUDE_DIRS})
else()
message("FFmpeg not found, defaulting to 0")
add_definitions(-DLV_USE_FFMPEG=0)
endif()
else()
message("Disable FFmpeg for Windows")
add_definitions(-DLV_USE_FFMPEG=0)
endif()
# libfreetype is required for the font test case
find_package(Freetype REQUIRED)
include_directories(${FREETYPE_INCLUDE_DIRS})
if(OPTIONS_SDL)
find_package(SDL2 REQUIRED)
include_directories(${SDL2_INCLUDE_DIRS})
endif()
# libinput is required for the libinput device driver test case
find_package(Libinput OPTIONAL_COMPONENTS)
if ($ENV{NON_AMD64_BUILD})
include_directories(${LIBINPUT_INCLUDE_DIRS})
endif()
# OpenGL ES is required for its driver
if ($ENV{NON_AMD64_BUILD})
message("Disable OpenGL, glfw3 for non-amd64 build")
add_definitions(-DLV_USE_OPENGLES=0)
else()
find_package(OpenGL)
find_package(glfw3)
if(OpenGL_FOUND AND glfw3_FOUND)
# Include directories
include_directories(${OPENGL_INCLUDE_DIR})
include_directories(${GLFW_INCLUDE_DIRS})
message("Enable LV_USE_OPENGLES")
else()
message("OpenGL, glfw3 not found, defaulting to 0")
add_definitions(-DLV_USE_OPENGLES=0)
endif()
endif()
if (NOT LIBINPUT_FOUND)
message("libinput not found, defaulting to 0")
add_definitions(-DLV_USE_LIBINPUT=0)
endif()
find_package(PkgConfig)
pkg_check_modules(xkbcommon pkg_check_modules xkbcommon)
if (NOT xkbcommon_FOUND)
message("xkbcommon not found, defaulting to 0")
add_definitions(-DLV_LIBINPUT_XKB=0)
endif()
# libdrm is required for the DRM display driver test case
include(${CMAKE_CURRENT_LIST_DIR}/FindLibDRM.cmake)
if(Libdrm_FOUND)
include_directories(${Libdrm_INCLUDE_DIRS})
else()
message("libdrm not found, defaulting to 0")
add_definitions(-DLV_USE_LINUX_DRM=0)
endif()
# If we are running on mac, set LV_USE_LINUX_FBDEV to 0
if(APPLE)
add_definitions(-DLV_USE_LINUX_FBDEV=0)
endif()
if(WIN32)
add_definitions(-DLV_USE_LINUX_FBDEV=0)
add_definitions(-DLV_USE_WINDOWS=1)
add_definitions(-DLV_USE_WAYLAND=0)
add_definitions(-DLV_USE_OS=LV_OS_WINDOWS)
endif()
# disable test targets for build only tests
if (ENABLE_TESTS)
file(GLOB_RECURSE TEST_CASE_FILES src/test_cases/*.c)
file(GLOB_RECURSE TEST_LIBS_FILES src/test_libs/*.c)
else()
set(TEST_CASE_FILES)
set(TEST_LIBS_FILES)
endif()
# build a test libs target
if (TEST_LIBS_FILES)
add_library(test_libs STATIC ${TEST_LIBS_FILES})
target_include_directories(test_libs PUBLIC ${TEST_INCLUDE_DIRS} "src/test_libs")
target_compile_definitions(test_libs PRIVATE LV_BUILD_TEST ${LVGL_COMPILER_DEFINES})
target_compile_options(test_libs PUBLIC ${LVGL_TESTFILE_COMPILE_OPTIONS})
list(APPEND TEST_LIBS test_libs)
endif()
foreach( test_case_fname ${TEST_CASE_FILES} )
# If test file is foo/bar/baz.c then test_name is "baz".
get_filename_component(test_name ${test_case_fname} NAME_WLE)
if (${test_name} STREQUAL "_test_template")
continue()
endif()
# gather all test cases
list(APPEND TEST_CASES ${test_name})
# Create path to auto-generated source file.
set(test_runner_fname ${CMAKE_CURRENT_BINARY_DIR}/${test_name}_Runner.c)
# Run ruby to generate source in build directory
add_custom_command(
OUTPUT ${test_runner_fname}
COMMAND ${RUBY_EXECUTABLE} ${generate_test_runner_rb}
${test_case_fname} ${test_runner_fname}
${generate_test_runner_config}
DEPENDS ${generate_test_runner_rb} ${test_case_fname}
${generate_test_runner_config}
)
add_executable( ${test_name}
${test_case_fname}
${test_runner_fname}
)
target_link_libraries(${test_name} PRIVATE
test_common
lvgl_demos
lvgl
lvgl_thorvg
${PNG_LIBRARIES}
${FREETYPE_LIBRARIES}
${LIBDRM_LIBRARIES}
${LIBINPUT_LIBRARIES}
${JPEG_LIBRARIES}
${WebP_LINK_LIBRARIES}
PkgConfig::FFMPEG
m
pthread
${TEST_LIBS})
if(OPTIONS_SDL)
target_link_libraries(${test_name} PRIVATE ${SDL_LIBRARY})
endif()
if (NOT $ENV{NON_AMD64_BUILD})
target_link_libraries(${test_name} PRIVATE
${OPENGL_LIBRARIES} glfw)
endif()
target_include_directories(${test_name} PUBLIC ${TEST_INCLUDE_DIRS})
target_compile_options(${test_name} PUBLIC ${LVGL_TESTFILE_COMPILE_OPTIONS})
add_test(
NAME ${test_name}
WORKING_DIRECTORY ${LVGL_TEST_DIR}
COMMAND ${test_name})
endforeach( test_case_fname ${TEST_CASE_FILES} )
add_custom_target(run
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 300
WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}
DEPENDS ${TEST_CASES}
USES_TERMINAL
)
endif()

View File

@@ -0,0 +1,24 @@
FROM --platform=linux/amd64 ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN dpkg --add-architecture i386
RUN apt update
COPY ./scripts/prerequisites-apt.txt .
COPY ./scripts/prerequisites-pip.txt .
RUN cat prerequisites-apt.txt | xargs apt install -y
RUN pip install --break-system-packages -r prerequisites-pip.txt
RUN git clone https://github.com/kornelski/pngquant && \
cd pngquant && \
git checkout 2.17.0 && \
./configure && \
make -j$(nproc) && \
make install
RUN rm -rf pngquant
WORKDIR /work

View File

@@ -0,0 +1,45 @@
find_package(PkgConfig)
pkg_check_modules(PKG_Libdrm pkg_check_modules libdrm)
set(Libdrm_DEFINITIONS ${PKG_Libdrm_CFLAGS_OTHER})
set(Libdrm_VERSION ${PKG_Libdrm_VERSION})
find_path(Libdrm_INCLUDE_DIR
NAMES
xf86drm.h
HINTS
${PKG_Libdrm_INCLUDE_DIRS}
)
find_library(Libdrm_LIBRARY
NAMES
drm
HINTS
${PKG_Libdrm_LIBRARY_DIRS}
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Libdrm
FOUND_VAR
Libdrm_FOUND
REQUIRED_VARS
Libdrm_LIBRARY
Libdrm_INCLUDE_DIR
VERSION_VAR
Libdrm_VERSION
)
if(Libdrm_FOUND AND NOT TARGET Libdrm::Libdrm)
add_library(Libdrm::Libdrm UNKNOWN IMPORTED)
set_target_properties(Libdrm::Libdrm PROPERTIES
IMPORTED_LOCATION "${Libdrm_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${Libdrm_DEFINITIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${Libdrm_INCLUDE_DIR}"
INTERFACE_INCLUDE_DIRECTORIES "${Libdrm_INCLUDE_DIR}/libdrm"
)
endif()
mark_as_advanced(Libdrm_LIBRARY Libdrm_INCLUDE_DIR)
set(Libdrm_LIBRARIES ${Libdrm_LIBRARY})
set(Libdrm_INCLUDE_DIRS ${Libdrm_INCLUDE_DIR} "${Libdrm_INCLUDE_DIR}/libdrm")
set(Libdrm_VERSION_STRING ${Libdrm_VERSION} ${PKG_Libdrm_LIBRARY_DIRS})

View File

@@ -0,0 +1,152 @@
# Tests for LVGL
## Test types available
- **Unit Tests**: Standard functional tests in `src/test_cases/` with screenshot comparison capabilities
- **Performance Tests**: ARM-emulated benchmarks in `src/test_cases_perf/` running on QEMU/SO3 environment
- **Emulated Benchmarks**: Automated `lv_demo_benchmark` runs in ARM emulation to prevent performance regressions
All of the tests are automatically ran in LVGL's CI.
## Quick start
- **Local Testing**: Run `./tests/main.py test` (after `scripts/install-prerequisites.sh`)
- **Docker Testing**: Build with `docker build . -f tests/Dockerfile -t lvgl_test_env` then run
- **Performance Testing**: Use `./tests/perf.py test` (requires Docker + Linux)
- **Benchmark Testing**: Use `./tests/benchmark_emu.py run` for emulated performance benchmarks (requires Docker + Linux)
---
## Running locally
### Local
1. Install requirements by:
```sh
scripts/install-prerequisites.sh
```
2. Run all executable tests with `./tests/main.py test`.
3. Build all build-only tests with `./tests/main.py build`.
4. Clean prior test build, build all build-only tests,
run executable tests, and generate code coverage
report `./tests/main.py --clean --report build test`.
5. You can re-generate the test images by adding option `--update-image`.
It relies on scripts/LVGLImage.py, which requires pngquant and pypng.
You can run below command firstly and follow instructions in logs to install them.
`./tests/main.py --update-image test`
Note that different version of pngquant may generate different images.
As of now the generated image on CI uses pngquant 2.13.1-1.
For full information on running tests run: `./tests/main.py --help`.
### Docker
To run the tests in an environment matching the CI setup:
1. Build it
```bash
docker build . -f tests/Dockerfile -t lvgl_test_env
```
2. Run the tests
```bash
docker run --rm -it -v $(pwd):/work lvgl_test_env "./tests/main.py"
```
This ensures you are testing in a consistent environment with the same dependencies as the CI pipeline.
## Running automatically
GitHub's CI automatically runs these tests on pushes and pull requests to `master` and `release/v8.*` branches.
## Directory structure
- `src` Source files of the tests
- `test_cases` The written tests,
- `test_cases_perf` The performance tests,
- `test_runners` Generated automatically from the files in `test_cases`.
- other miscellaneous files and folders
- `ref_imgs` - Reference images for screenshot compare
- `report` - Coverage report. Generated if the `report` flag was passed to `./main.py`
- `unity` Source files of the test engine
## Add new tests
### Create new test file
New test needs to be added into the `src/test_cases` folder. The name of the files should look like `test_<name>.c`. The basic skeleton of a test file copy `_test_template.c`.
### Asserts
See the list of asserts [here](https://github.com/ThrowTheSwitch/Unity/blob/master/docs/UnityAssertionsReference.md).
There are some custom, LVGL specific asserts:
- `TEST_ASSERT_EQUAL_SCREENSHOT("image1.png")` Render the active screen and compare its content with an image in the `ref_imgs` folder.
- If the reference image is not found it will be created automatically from the rendered screen.
- If the compare fails an `<image_name>_err.png` file will be created with the rendered content next to the reference image.
- `TEST_ASSERT_EQUAL_COLOR(color1, color2)` Compare two colors.
## Performance Tests
### Requirements
- **Docker**
- **Linux host machine** (WSL *may* work but is untested)
### Running Tests
The performance tests are run inside a Docker container that launches an ARM emulated environment using QEMU to ensure consistent timing across machines.
Each test runs on a lightweight ARM-based OS ([SO3](https://github.com/smartobjectoriented/so3)) within this emulated environment.
To run the tests:
```bash
./perf.py [--clean] [--auto-clean] [--test-suite <suite>] [--build-options <option>] [build|generate|test]
```
- `build` and `generate`: generates all necessary build and configuration files
- `test`: launches Docker with the appropriate volume mounts and runs the tests inside the container
> [!NOTE]
> Building doesn't actually build the source files because the current docker image doesn't separate the building and running. Instead, it does both
You can specify different build configurations via `--build-options`, and optionally filter tests using `--test-suite`.
For full usage options, run:
```sh
./perf.py --help
```
You can also run this script by passing a performance test config to the `main.py` script. The performance tests configs can be found inside the [`perf.py`](./perf.py) file
## Emulated benchmarks
In addition to unit and performance tests, LVGL automatically runs the `lv_demo_benchmark` inside the same ARM emulated
environment mentionned in the previous section through CI to prevent unintentional slowdowns.
### Requirements
- **Docker**
- **Linux host machine** (WSL *may* work but is untested)
To run the these benchmarks in the emulated setup described above, you can use the provided python script:
```sh
./benchmark_emu.py [-h] [--config {perf32b,perf64b}] [--pull] [--clean] [--auto-clean]
[{generate,run} ...]
```
The following command runs all available configurations:
```sh
./benchmark_emu.py run
```
You can also request a specific configuration:
```sh
./benchmark_emu.py --config perf32b run
```

View File

@@ -0,0 +1,288 @@
#!/usr/bin/env python3
import argparse
import sys
import os
import shutil
import subprocess
benchmark_configs = {
"perf32b": {
"description": "Benchmark config ARM (so3) Emulated - 32 bit",
"image_name": "ghcr.io/smartobjectoriented/so3-lvperf32b:main",
"defaults_file": "lv_conf_perf32b.defaults",
},
"perf64b": {
"description": "Benchmark config ARM (so3) Emulated - 64 bit",
"image_name": "ghcr.io/smartobjectoriented/so3-lvperf64b:main",
"defaults_file": "lv_conf_perf64b.defaults",
},
}
lvgl_test_dir = os.path.dirname(os.path.realpath(__file__))
lvgl_root_dir = os.path.dirname(lvgl_test_dir)
def main() -> int:
epilog = """This program runs the LVGL demo benchmark
In order to provide timing consistency between host computers,
these runs are run in an ARM emulated environment inside QEMU.
For the runtime environment, SO3 is used which is a lightweight, ARM-based
operating system.
Right now, this script requires a host linux computer as we depend on
`losetup` which is used to set up and control loop devices.
"""
parser = argparse.ArgumentParser(
description="Run LVGL benchmark tests.", epilog=epilog
)
parser.add_argument(
"--config",
nargs=1,
choices=benchmark_configs.keys(),
help="The benchmark config to use. Available configs: "
+ ", ".join(benchmark_configs.keys()),
required=False,
)
parser.add_argument(
"--pull",
action="store_true",
default=False,
help="Pull latest images from registry before running tests",
)
parser.add_argument(
"--clean",
action="store_true",
default=False,
help="Clean existing build artifacts before operation",
)
parser.add_argument(
"--auto-clean",
action="store_true",
default=False,
help="Automatically clean build directories",
)
parser.add_argument(
"actions",
nargs="*",
choices=["generate", "run"],
help="generate: generate the lv_conf.h from the `configs/ci/perf` folder, run: run the benchmark with the provided config",
)
args = parser.parse_args()
if not args.actions:
print("Error: At least one action must be specified")
parser.print_help()
return 1
configs = []
if args.config:
configs = args.config
else:
configs = benchmark_configs.keys()
for config in configs:
if args.clean:
clean(config)
if "generate" in args.actions:
generate_benchmark_files(config)
if "run" in args.actions:
run_benchmark(config, args.pull)
if args.auto_clean:
clean(config)
return 0
def get_build_dir(config_name: str) -> str:
"""Given the config name, return the build directory path."""
build_dir_name = f"build_benchmark_{config_name}"
return os.path.join(lvgl_test_dir, build_dir_name)
def get_container_name(config_name: str) -> str:
"""
Returns the docker container name based on the config name
"""
return f"lv_benchmark_{config_name}"
def get_docker_volumes(config_name: str) -> list[str]:
"""
Returns all docker volume names that should be created
"""
return [get_build_cache_volume(config_name), get_disk_cache_volume(config_name)]
def get_build_cache_volume(config_name: str) -> str:
"""
Returns the docker volume name for storing cmake generated files
"""
return f"{get_container_name(config_name)}_build_cache"
def get_disk_cache_volume(config_name: str) -> str:
"""
Returns the docker volume name for storing the virtual disks
"""
return f"{get_container_name(config_name)}_disk_cache"
def create_dir_if_not_exists(build_dir: str) -> bool:
"""Create directory if it doesn't exist"""
created_build_dir = False
if os.path.exists(build_dir):
if not os.path.isdir(build_dir):
raise ValueError(f"{build_dir} exists but is not a directory")
else:
os.makedirs(build_dir, exist_ok=True)
created_build_dir = True
return created_build_dir
def generate_config(config_name: str) -> None:
"""Generate lv_conf.h from the defaults file for the given config"""
print(f"Generating config for {config_name}")
build_dir = get_build_dir(config_name)
create_dir_if_not_exists(build_dir)
defaults_file = benchmark_configs[config_name]["defaults_file"]
defaults_path = os.path.join(lvgl_root_dir, "configs", "ci", "perf", defaults_file)
template_path = os.path.join(lvgl_root_dir, "lv_conf_template.h")
assert os.path.exists(defaults_path)
assert template_path
output_path = os.path.join(build_dir, "lv_conf.h")
generate_script = os.path.join(lvgl_root_dir, "scripts", "generate_lv_conf.py")
cmd = [
sys.executable,
generate_script,
"--template",
template_path,
"--config",
output_path,
"--defaults",
defaults_path,
build_dir,
]
subprocess.check_call(cmd)
print(f"Generated lv_conf.h at {output_path}")
def generate_benchmark_files(config_name: str) -> None:
"""Generate the necessary files for running benchmark inside so3"""
print()
print()
label = f"Generating benchmark files for: {config_name}: {benchmark_configs[config_name]['description']}"
print("=" * len(label))
print(label)
print("=" * len(label))
build_dir = get_build_dir(config_name)
create_dir_if_not_exists(build_dir)
generate_config(config_name)
def clean(config_name: str) -> None:
"""Clean build directory and docker resources"""
print(f"Cleaning resources for {config_name}")
build_dir = get_build_dir(config_name)
container_name = get_container_name(config_name)
if os.path.exists(build_dir):
shutil.rmtree(build_dir)
print(f"Removed build directory: {build_dir}")
subprocess.check_call(
["docker", "rm", "-f", container_name],
)
for volume in get_docker_volumes(config_name):
subprocess.check_call(
["docker", "volume", "remove", "-f", volume],
)
def run_benchmark(config_name: str, pull: bool) -> None:
"""Run the benchmark using docker"""
def volume(src, dst):
return ["-v", f"{src}:{dst}"]
def so3_usr_lib(path):
return f"/so3/usr/lib/{path}"
so3_usr_build = "/so3/usr/build"
persistence_dir = "/persistence"
container_name = get_container_name(config_name)
build_dir = get_build_dir(config_name)
for v in get_docker_volumes(config_name):
subprocess.check_call(["docker", "volume", "create", v])
lvgl_src_path = os.path.join(lvgl_root_dir, "src")
lvgl_h_path = os.path.join(lvgl_root_dir, "lvgl.h")
lvgl_demos_path = os.path.join(lvgl_root_dir, "demos")
lv_conf_path = os.path.join(build_dir, "lv_conf.h")
if not os.path.exists(lv_conf_path):
generate_config(config_name)
assert os.path.exists(lv_conf_path)
docker_image_name = benchmark_configs[config_name]["image_name"]
volumes = [
# This is necessary to create a loop device
volume("/dev", "/dev"),
# Replace container's lvgl source and lv_conf
volume(lvgl_h_path, so3_usr_lib("lvgl/lvgl.h")),
volume(lvgl_src_path, so3_usr_lib("lvgl/src")),
volume(lvgl_demos_path, so3_usr_lib("lvgl/demos")),
volume(lv_conf_path, so3_usr_lib("lv_conf.h")),
# Cache build and disk folders so we don't regenerate everything in consecutive runs
volume(get_build_cache_volume(config_name), so3_usr_build),
volume(get_disk_cache_volume(config_name), persistence_dir),
]
interactive = "-it" if sys.stdout.isatty() else "-t"
command = [
"docker",
"run",
"--rm",
"--privileged",
interactive,
"--name",
container_name,
]
if pull:
command.append("--pull=always")
for v in volumes:
command.extend(v)
command.append(docker_image_name)
print()
print()
label = f"Running benchmark: {config_name}: {benchmark_configs[config_name]['description']}"
print("=" * len(label))
print(label)
print("=" * len(label))
subprocess.check_call(command)
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,5 @@
:unity:
:includes:
- lv_test_init.h
:suite_setup: "lv_test_init();"
:suite_teardown: "lv_test_deinit();return num_failures;"

View File

@@ -0,0 +1,41 @@
# Basic test to see if the API jeson generator is able to run without any errors
# This test doesn't not check to make sure the output is correct. It is for the
# sole purpose of making sure it completes.
import os
import sys
BASE_PATH = os.path.abspath(os.path.dirname(__file__))
SCRIPT_PATH = os.path.join(
BASE_PATH, '..', '..', 'scripts',
'gen_json', 'gen_json.py'
)
OUTPUT_FILE = os.path.join(BASE_PATH, 'lvgl.json')
cmd = f'{sys.executable} "{SCRIPT_PATH}" --output-path "{BASE_PATH}"'
print('running test')
print(cmd)
result = os.system(cmd)
if result != 0:
print()
sys.stderr.write('TEST FAILED!!\n\n')
sys.stderr.flush()
sys.exit(result)
if not os.path.exists(OUTPUT_FILE):
print()
sys.stderr.write(f'"{OUTPUT_FILE}" was not found.\n')
sys.stderr.write('TEST FAILED!!\n\n')
sys.stderr.flush()
sys.exit(-500)
try:
os.remove(OUTPUT_FILE)
except: # NOQA
pass
print()
print('TEST PASSED!')

View File

@@ -0,0 +1,320 @@
#!/usr/bin/env python3
import argparse
import shutil
import subprocess
import sys
import os
import platform
from itertools import chain
from pathlib import Path
lvgl_test_dir = os.path.dirname(os.path.realpath(__file__))
lvgl_script_path = os.path.join(lvgl_test_dir, "../scripts")
sys.path.append(lvgl_script_path)
wayland_dir = os.path.join(lvgl_test_dir, "wayland_protocols")
wayland_protocols_dir = os.path.realpath("/usr/share/wayland-protocols")
from perf import perf_test_options
from LVGLImage import LVGLImage, ColorFormat, CompressMethod
# Key values must match variable names in CMakeLists.txt.
build_only_options = {
'OPTIONS_NORMAL_8BIT': 'Normal config, 8 bit color depth',
'OPTIONS_16BIT': 'Minimal config, 16 bit color depth',
'OPTIONS_24BIT': 'Normal config, 24 bit color depth',
'OPTIONS_FULL_32BIT': 'Full config, 32 bit color depth',
}
if platform.system() != 'Windows':
build_only_options['OPTIONS_SDL'] = 'SDL simulator with full config, 32 bit color depth'
test_options = {
'OPTIONS_TEST_SYSHEAP': 'Test config, system heap, 32 bit color depth',
'OPTIONS_TEST_DEFHEAP': 'Test config, LVGL heap, 32 bit color depth',
'OPTIONS_TEST_VG_LITE': 'VG-Lite simulator with full config, 32 bit color depth',
'OPTIONS_TEST_RISCV_V': 'RISC-V Vector emulation with full config, 32 bit color depth',
}
def get_option_description(option_name):
if option_name in build_only_options:
return build_only_options[option_name]
return test_options[option_name]
def delete_dir_ignore_missing(dir_path):
'''Recursively delete a directory and ignore if missing.'''
try:
shutil.rmtree(dir_path)
except FileNotFoundError:
pass
def options_abbrev(options_name):
'''Return an abbreviated version of the option name.'''
prefix = 'OPTIONS_'
assert options_name.startswith(prefix)
return options_name[len(prefix):].lower()
def get_base_build_dir(options_name):
'''Given the build options name, return the build directory name.
Does not return the full path to the directory - just the base name.'''
return 'build_%s' % options_abbrev(options_name)
def get_build_dir(options_name):
'''Given the build options name, return the build directory name.
Returns absolute path to the build directory.'''
global lvgl_test_dir
return os.path.join(lvgl_test_dir, get_base_build_dir(options_name))
def gen_wayland_protocols(clean):
'''Generates the xdg shell interface from wayland protocol definitions'''
if clean:
delete_dir_ignore_missing(wayland_dir)
if not os.path.isdir(wayland_dir):
os.mkdir(wayland_dir)
subprocess.check_call(['wayland-scanner',
'client-header',
os.path.join(wayland_protocols_dir, "stable/xdg-shell/xdg-shell.xml"),
os.path.join(wayland_dir, "wayland_xdg_shell.h.original"),
])
subprocess.check_call(['wayland-scanner',
'private-code',
os.path.join(wayland_protocols_dir, "stable/xdg-shell/xdg-shell.xml"),
os.path.join(wayland_dir, "wayland_xdg_shell.c.original"),
])
# Insert guards
with open(os.path.join(wayland_dir, "wayland_xdg_shell.h"), "w") as outfile:
subprocess.check_call(['sed','-e', "1i #if LV_BUILD_TEST", '-e', '$a #endif',
os.path.join(wayland_dir, "wayland_xdg_shell.h.original")], stdout=outfile)
with open(os.path.join(wayland_dir, "wayland_xdg_shell.c"), "w") as outfile:
subprocess.check_call(['sed','-e', "1i #if LV_BUILD_TEST", '-e', '$a #endif',
os.path.join(wayland_dir, "wayland_xdg_shell.c.original")], stdout=outfile)
subprocess.check_call(['rm', os.path.join(wayland_dir, "wayland_xdg_shell.c.original")])
subprocess.check_call(['rm', os.path.join(wayland_dir, "wayland_xdg_shell.h.original")])
def build_tests(options_name, build_type, clean):
'''Build all tests for the specified options name.'''
global lvgl_test_dir
print()
print()
label = 'Building: %s: %s' % (options_abbrev(
options_name), get_option_description(options_name))
print('=' * len(label))
print(label)
print('=' * len(label))
print(flush=True)
build_dir = get_build_dir(options_name)
if clean:
delete_dir_ignore_missing(build_dir)
os.chdir(lvgl_test_dir)
if platform.system() != 'Windows':
gen_wayland_protocols(clean)
created_build_dir = False
if not os.path.isdir(build_dir):
os.mkdir(build_dir)
created_build_dir = True
os.chdir(build_dir)
if created_build_dir:
subprocess.check_call(['cmake', '-GNinja', '-DCMAKE_BUILD_TYPE=%s' % build_type,
'-D%s=1' % options_name, '..'])
subprocess.check_call(['cmake', '--build', build_dir,
'--parallel', str(os.cpu_count())])
def run_tests(options_name, test_suite):
'''Run the tests for the given options name.'''
print()
print()
label = 'Running tests for %s' % options_abbrev(options_name)
print('=' * len(label))
print(label)
print('=' * len(label), flush=True)
os.chdir(get_build_dir(options_name))
args = [
'ctest',
'--timeout', '300',
'--parallel', str(os.cpu_count()),
'--output-on-failure',
]
if test_suite is not None:
args.extend(["--tests-regex", test_suite])
subprocess.check_call(args)
def generate_code_coverage_report():
'''Produce code coverage test reports for the test execution.'''
global lvgl_test_dir
print()
print()
label = 'Generating code coverage reports'
print('=' * len(label))
print(label)
print('=' * len(label))
print(flush=True)
os.chdir(lvgl_test_dir)
delete_dir_ignore_missing('report')
os.mkdir('report')
root_dir = os.path.dirname(lvgl_test_dir) # Get parent directory of tests (lvgl directory)
html_report_file = 'report/index.html'
cmd = ['gcovr', '--gcov-ignore-parse-errors',
'--root', root_dir, '--html-details', '--output',
html_report_file, '--xml', 'report/coverage.xml',
'-j', str(os.cpu_count()), '--print-summary', '--merge-mode-functions=merge-use-line-min',
'--html-title', 'LVGL Test Coverage', '--filter', os.path.join(root_dir, 'src/.*/lv_.*\\.c')]
subprocess.check_call(cmd)
print("Done: See %s" % html_report_file, flush=True)
def generate_test_images():
invalids = (ColorFormat.UNKNOWN,ColorFormat.RAW,ColorFormat.RAW_ALPHA)
formats = [i for i in ColorFormat if i not in invalids]
png_path = os.path.join(lvgl_test_dir, "test_images/pngs")
pngs = list(Path(png_path).rglob("*.[pP][nN][gG]"))
print(f"png files: {pngs}")
align_options = [1, 64]
for align in align_options:
for compress in CompressMethod:
compress_name = compress.name if compress != CompressMethod.NONE else "UNCOMPRESSED"
outputs = os.path.join(lvgl_test_dir, f"test_images/stride_align{align}/{compress_name}/")
os.makedirs(outputs, exist_ok=True)
for fmt in formats:
for png in pngs:
img = LVGLImage().from_png(png, cf=fmt, background=0xffffff)
img.adjust_stride(align=16)
output = os.path.join(outputs, f"{Path(png).stem}_{fmt.name}.bin")
img.to_bin(output, compress=compress)
output = os.path.join(outputs, f"{Path(png).stem}_{fmt.name}_{compress.name}_align{align}.c")
img.to_c_array(output, compress=compress)
print(f"converting {os.path.basename(png)}, format: {fmt.name}, compress: {compress_name}")
def clean_build_dirs_with_filter(build_dir, clean_filters):
for entry in os.listdir(build_dir):
entry_path = os.path.join(build_dir, entry)
if any(entry.endswith(filter) for filter in clean_filters):
continue
if os.path.isfile(entry_path):
os.remove(entry_path)
elif os.path.isdir(entry_path):
shutil.rmtree(entry_path)
if __name__ == "__main__":
epilog = '''This program builds and optionally runs the LVGL test programs.
There are two types of LVGL tests: "build", and "test". The build-only
tests, as their name suggests, only verify that the program successfully
compiles and links (with various build options). There are also a set of
tests that execute to verify correct LVGL library behavior.
'''
parser = argparse.ArgumentParser(
description='Build and/or run LVGL tests.', epilog=epilog)
parser.add_argument('--build-options', nargs=1,
choices=list(chain(build_only_options, test_options, perf_test_options)),
help='''the build option name to build or run. When
omitted all build configurations are used.
''')
parser.add_argument('--clean', action='store_true', default=False,
help='clean existing build artifacts before operation.')
parser.add_argument('--report', action='store_true',
help='generate code coverage report for tests.')
parser.add_argument('actions', nargs='*', choices=['build', 'test'],
help='build: compile build tests, test: compile/run executable tests.')
parser.add_argument('--test-suite', default=None,
help='select test suite to run')
parser.add_argument('--update-image', action='store_true', default=False,
help='Update test image using LVGLImage.py script')
parser.add_argument('--auto-clean', action='store_true', default=False,
help='Automatically clean build directories')
parser.add_argument('--keep-report', action='store_true', default=False,
help='Skip cleaning gcov report files when --auto-clean and --report are enabled')
args = parser.parse_args()
if args.update_image:
generate_test_images()
if args.build_options:
options_to_build = args.build_options
else:
if 'build' in args.actions:
if 'test' in args.actions:
options_to_build = {**build_only_options, **test_options}
else:
options_to_build = build_only_options
else:
options_to_build = test_options
clean_build_dirs = []
for options_name in options_to_build:
is_test = options_name in test_options
is_perf_test = options_name in perf_test_options
if is_perf_test:
perf_test_script = os.path.join(lvgl_test_dir, "perf.py")
try:
subprocess.check_call([perf_test_script, *(sys.argv[1:])])
except subprocess.CalledProcessError as e:
sys.exit(e.returncode)
continue
build_type = 'Debug'
build_tests(options_name, build_type, args.clean)
if is_test:
try:
run_tests(options_name, args.test_suite)
except subprocess.CalledProcessError as e:
sys.exit(e.returncode)
if args.auto_clean:
build_dir = get_build_dir(options_name)
if args.report:
# Keep the files that gcovr analysis depends on and delete
# the rest to solve the storage capacity limit of GitHub CI
clean_filters = ['CMakeFiles', '.c']
clean_build_dirs_with_filter(build_dir, clean_filters)
if args.keep_report:
# Retain the gcov report files for subsequent automated coverage analysis.
print(f"Keeping {build_dir} for report")
else:
print(f"Append {build_dir} to clean list")
clean_build_dirs.append(build_dir)
else:
# Remove all build directories directly
print(f"Removing {build_dir}")
shutil.rmtree(build_dir)
if args.report:
generate_code_coverage_report()
# Clean all build directories after report
for build_dir in clean_build_dirs:
print(f"Removing {build_dir}")
shutil.rmtree(build_dir)

View File

@@ -0,0 +1 @@
test_file

View File

@@ -0,0 +1,10 @@
LVGL_PATH := $(CURDIR)/../..
$(info LVGL_PATH is set to: $(LVGL_PATH))
include ../../lvgl.mk
CSRCS += test.c
CFLAGS += -Werror -DLV_CONF_SKIP=1 -DLV_BUILD_TEST=1 -I$(LVGL_PATH)/..
COBJS := $(patsubst %.c, %.o, $(CSRCS))
test_file: $(COBJS)
$(CC) -o $@ $^

View File

@@ -0,0 +1,10 @@
#if LV_BUILD_TEST
#include <stdio.h>
#include "../../lvgl.h"
int main(void)
{
lv_init();
return 0;
}
#endif

View File

@@ -0,0 +1 @@
test_file

View File

@@ -0,0 +1,38 @@
LVGL_PATH := $(CURDIR)/../..
ARCH = x86_64
CC = clang
$(info LVGL_PATH is set to: $(LVGL_PATH))
include ../../lvgl.mk
CSRCS += test.c
CFLAGS = -Werror -I$(LVGL_PATH)/.. -I$(CURDIR) \
-target $(ARCH)-unknown-windows -ffreestanding -fshort-wchar -mno-red-zone -fno-stack-protector -fno-stack-check -mno-red-zone \
-DLV_CONF_SKIP=1 \
-DLV_BUILD_TEST=1 \
-DLV_BUILD_EXAMPLES=0 \
-DLV_COLOR_DEPTH=32 \
-DLV_USE_STDLIB_MALLOC=LV_STDLIB_BUILTIN \
-DLV_USE_STDLIB_STRING=LV_STDLIB_BUILTIN \
-DLV_USE_STDLIB_SPRINTF=LV_STDLIB_BUILTIN \
-DLV_STDINT_INCLUDE=\"efi.h\" \
-DLV_STDINT_INCLUDE=\"efi.h\" \
-DLV_STDDEF_INCLUDE=\"efi.h\" \
-DLV_STDBOOL_INCLUDE=\"efi.h\" \
-DLV_INTTYPES_INCLUDE=\"efi.h\" \
-DLV_LIMITS_INCLUDE=\"efi.h\" \
-DLV_STDARG_INCLUDE=\"efi.h\"
LDFLAGS = -target $(ARCH)-unknown-windows -nostdlib -Wl,-entry:efi_main -Wl,-subsystem:efi_application -fuse-ld=lld-link
COBJS := $(patsubst $(LVGL_PATH)%,objs/%, $(CSRCS:.c=.o))
test_file: $(COBJS)
$(CC) -o $@ $^ $(LDFLAGS)
objs/%.o: $(LVGL_PATH)%.c
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -o $@ -c $^
clean:
rm -r $(CURDIR)/objs
rm test_file

View File

@@ -0,0 +1,160 @@
#ifndef __LVGL_TESTS_MAKEFILE_UEFI_EFI_H__
#define __LVGL_TESTS_MAKEFILE_UEFI_EFI_H__
#ifndef __clang__
#error This file is only for use with the clang compiler
#endif
/*************************************
* TYPES
*************************************/
#if defined(__x86_64__)
typedef unsigned long long UINT64;
typedef long long INT64;
typedef unsigned int UINT32;
typedef int INT32;
typedef unsigned short UINT16;
typedef unsigned short CHAR16;
typedef short INT16;
typedef unsigned char BOOLEAN;
typedef unsigned char UINT8;
typedef char CHAR8;
typedef signed char INT8;
typedef UINT64 UINTN;
typedef INT64 INTN;
typedef INT64 INTMAX;
#elif defined(__i386__)
typedef unsigned long long UINT64;
typedef long long INT64;
typedef unsigned int UINT32;
typedef int INT32;
typedef unsigned short UINT16;
typedef unsigned short CHAR16;
typedef short INT16;
typedef unsigned char BOOLEAN;
typedef unsigned char UINT8;
typedef char CHAR8;
typedef signed char INT8;
typedef UINT32 UINTN;
typedef INT32 INTN;
typedef INT64 INTMAX;
#elif defined(__aarch64__)
typedef unsigned long long UINT64;
typedef long long INT64;
typedef unsigned int UINT32;
typedef int INT32;
typedef unsigned short UINT16;
typedef unsigned short CHAR16;
typedef short INT16;
typedef unsigned char BOOLEAN;
typedef unsigned char UINT8;
typedef char CHAR8;
typedef signed char INT8;
typedef UINT64 UINTN;
typedef INT64 INTN;
typedef INT64 INTMAX;
#else
#error Architecture is not supported
#endif
typedef UINT8 uint8_t;
typedef UINT16 uint16_t;
typedef UINT32 uint32_t;
typedef UINT64 uint64_t;
typedef INT8 int8_t;
typedef INT16 int16_t;
typedef INT32 int32_t;
typedef INT64 int64_t;
typedef void VOID;
typedef uint32_t uint_fast32_t;
typedef UINTN uintptr_t;
typedef UINTN size_t;
typedef INTN intptr_t;
typedef INTMAX intmax_t;
typedef INTN ptrdiff_t;
typedef UINT8 bool;
/*************************************
* DEFINES
*************************************/
#define false 0
#define true 1
#define NULL ((void*)0)
#define PRId8 "d"
#define PRId16 "d"
#define PRId32 "d"
#define PRId64 "d"
#define PRIu8 "u"
#define PRIu16 "u"
#define PRIu32 "u"
#define PRIu64 "u"
#define PRIx8 "x"
#define PRIx16 "x"
#define PRIx32 "x"
#define PRIx64 "x"
#define PRIX8 "X"
#define PRIX16 "X"
#define PRIX32 "X"
#define PRIX64 "X"
#define offsetof(TYPE, Field) ((UINTN) __builtin_offsetof(TYPE, Field))
/*************************************
* LIMITS
*************************************/
#define INT8_MAX (0x7F)
#define UINT8_MAX (0xFF)
#define INT16_MAX (0x7FFF)
#define UINT16_MAX (0xFFFF)
#define INT32_MAX (0x7FFFFFFF)
#define UINT32_MAX (0xFFFFFFFF)
#define INT64_MAX (0x7FFFFFFFFFFFFFFFULL)
#define UINT64_MAX (0xFFFFFFFFFFFFFFFFULL)
#define INT_MAX (0x7FFFFFFFFFFFFFFFULL)
#define UINT_MAX (0xFFFFFFFFFFFFFFFFULL)
///
/// Minimum values for the signed UEFI Data Types
///
#define INT8_MIN (( -127) - 1)
#define INT16_MIN (( -32767) - 1)
#define INT32_MIN (( -2147483647) - 1)
#define INT64_MIN (( -9223372036854775807LL) - 1)
#define SIZE_MAX (0xFFFFFFFF)
#define LONG_MAX (0x7FFFFFFF)
#define CHAR_BIT 8
/*************************************
* VA_ARG
*************************************/
typedef __builtin_va_list va_list;
#define va_start(Marker, Parameter) __builtin_va_start (Marker, Parameter)
#define va_arg(Marker, TYPE) ((sizeof (TYPE) < sizeof (UINTN)) ? (TYPE)(__builtin_va_arg (Marker, UINTN)) : (TYPE)(__builtin_va_arg (Marker, TYPE)))
#define va_end(Marker) __builtin_va_end (Marker)
#define va_copy(Dest, Start) __builtin_va_copy (Dest, Start)
/*************************************
* VERIFICATION
*************************************/
_Static_assert(sizeof(bool) == 1, "Size check for 'bool' failed.");
_Static_assert(sizeof(int8_t) == 1, "Size check for 'int8_t' failed.");
_Static_assert(sizeof(uint8_t) == 1, "Size check for 'uint8_t' failed.");
_Static_assert(sizeof(int16_t) == 2, "Size check for 'int16_t' failed.");
_Static_assert(sizeof(uint16_t) == 2, "Size check for 'uint16_t' failed.");
_Static_assert(sizeof(int32_t) == 4, "Size check for 'int32_t' failed.");
_Static_assert(sizeof(uint32_t) == 4, "Size check for 'uint32_t' failed.");
_Static_assert(sizeof(uint_fast32_t) == 4, "Size check for 'uint_fast32_t' failed.");
_Static_assert(sizeof(int64_t) == 8, "Size check for 'int64_t' failed.");
_Static_assert(sizeof(uint64_t) == 8, "Size check for 'uint64_t' failed.");
_Static_assert(sizeof(intptr_t) == sizeof(void *), "Size check for 'intptr_t' failed.");
_Static_assert(sizeof(ptrdiff_t) == sizeof(void *), "Size check for 'ptrdiff_t' failed.");
_Static_assert(sizeof(uintptr_t) == sizeof(void *), "Size check for 'uintptr_t' failed.");
#endif

View File

@@ -0,0 +1,35 @@
#if LV_BUILD_TEST
#include "../../lvgl.h"
int efi_main(void * image_handle, void * system_table)
{
lv_init();
return 0;
}
// memcpy is required as symbol for the clang compiler
void * memcpy(void * s, const void * ct, size_t n)
{
const uint8_t * ct_8 = (const uint8_t *) ct;
uint8_t * s_8 = (uint8_t *) s;
while(n-- > 0) {
*s_8++ = *ct_8++;
}
return s;
}
// memset is required as symbol for the clang compiler
void * memset(void * s, int c, size_t n)
{
uint8_t * s_8 = (uint8_t *)s;
while(n-- > 0) {
*s_8++ = (uint8_t)c;
}
return s;
}
#endif

View File

@@ -0,0 +1,405 @@
"""
The test script will only run the one test on micropython. It is not a
framework that can be used for other tests. This has a reduced code footprint.
entrypoint API is almost the same as the framework.
Requirements
Python >= 3.10
pillow library
add the following to the CI prior to the test running
python3 -m pip install pillow
Example command line to run the test. I suggest doing this from the root of the
binding directory. It is just a simple location to do it from.
Paths that are passed in MUST be relative to the current working directory.
python3 lib/lv_bindings/lvgl/tests/micropy_test/__init__.py --artifact-path=lib/lv_bindings/lvgl/tests/micropy_test/artifacts --mpy-path=ports/unix/build-standard/micropython
"""
import os
import time
import signal
import argparse
import binascii
import unittest
import threading
import subprocess
from PIL import Image as Image
DEBUG = 0
debug_log = None
saved_test_data = []
def format_error_data(data):
output = ''
for line in data.split('\n'):
output += f'\033[31;1m{line}\033[0m\n'
return output
def log(*args):
args = ' '.join(repr(arg) for arg in args)
debug_log.write(args + '\n')
if DEBUG:
sys.stdout.write('\033[31;1m' + args + '\033[0m\n')
sys.stdout.flush()
BASE_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__)))
TEST_PATH = os.path.join(BASE_PATH, 'micropy.py')
IMG_PATH = os.path.join(BASE_PATH, '../ref_imgs/binding.png')
CTRL_C = b'\x03' # 2 times to exit any running code
CTRL_D = b'\x04' # exit paste mode committing and running what has been pasted
CTRL_E = b'\x05' # enter paste mode
PASTE_PROMPT = b'==='
REPL_PROMPT = b'>>>'
os.environ['MICROPYINSPECT'] = '1'
class TestData:
def __init__(self):
self.watchdog_timer = time.time()
self.result = None
self.error_data = ''
self.event = threading.Event()
class MicroPython_Test(unittest.TestCase):
# these are here simply to make an IDE happy. Their values get dynamically
# set when the class gets constructed
process: subprocess.Popen = None
exit_event: threading.Event = None
@classmethod
def send(cls, cmd):
if cls.process is None:
return
log('<---', cmd)
cls.process.stdin.write(cmd)
cls.process.stdin.flush()
@classmethod
def read_until(cls, marker):
micropy_data = b''
error_data = b''
log('MARKER', marker)
logged = False
while (
not micropy_data.endswith(marker) and
not cls.exit_event.is_set()
):
try:
char = cls.process.stdout.read(1)
except: # NOQA
break
if char:
micropy_data += char
logged = False
else:
logged = True
log('--->', micropy_data)
if b'\nERROR END\n' in micropy_data:
error_data = micropy_data.split(b'\nERROR START\n')[-1].split(b'\nERROR END\n')[0]
micropy_data = b''
try:
log('---> ERROR: ', error_data.decode('utf-8'))
except UnicodeDecodeError:
log('---> ERROR: ', error_data)
logged = True
break
if not logged:
log('--->', micropy_data)
if b'\nERROR END\n' in micropy_data:
error_data = micropy_data.split(b'\nERROR START\n')[-1].split(b'\nERROR END\n')[0]
micropy_data = b''
try:
log('---> ERROR: ', error_data.decode('utf-8'))
except UnicodeDecodeError:
log('---> ERROR: ', error_data)
if cls.exit_event.is_set():
log('--EXIT EVENT SET')
saved_test_data.append((micropy_data, error_data))
return micropy_data.replace(marker, b''), error_data
@classmethod
def setUpClass(cls):
os.chdir(os.path.dirname(__file__))
cls.exit_event = threading.Event()
log(f'--SETTING UP')
cls.process = subprocess.Popen(
['bash'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
env=os.environ,
shell=True,
preexec_fn=os.setsid
)
log(f'--RUNNING MICROPYTHON ({MICROPYTHON_PATH})')
cls.send(b'cd ' + os.path.dirname(__file__).encode('utf-8') + b'\n')
cls.send(MICROPYTHON_PATH.encode('utf-8') + b'\n')
_, error_data = cls.read_until(b'>>>')
if error_data:
raise RuntimeError(error_data)
log('--MICROPYTHON STARTED')
@classmethod
def tearDownClass(cls):
log('--TEARDOWN STARTED')
if cls.process is not None:
cls.send(CTRL_C)
cls.send(CTRL_C)
cls.send(CTRL_D)
if not cls.process.stdin.closed:
cls.process.stdin.close()
os.killpg(os.getpgid(cls.process.pid), signal.SIGTERM)
cls.process.wait()
if not cls.process.stdout.closed:
cls.process.stdout.close()
if not cls.process.stderr.closed:
cls.process.stderr.close()
cls.process = None
log(f'--TEARDOWN FINISHED')
def test_1_image_compare(self):
image = Image.open(IMG_PATH)
res_img = image.convert('RGB')
image.close()
res_data = list(res_img.getdata())
res_img.close()
with open(TEST_PATH, 'rb') as f:
test_code = f.read()
log(f'--RUNNING TEST ({TEST_PATH})')
test_code = test_code.strip()
if self.__class__.process is None:
self.fail('MicroPython failure.')
self.send(CTRL_E)
_, error_data = self.read_until(PASTE_PROMPT)
if error_data:
self.fail(error_data)
test_code = test_code.split(b'\r\n')
for i, line in enumerate(test_code):
self.send(line + b'\n')
_, error_data = self.read_until(b'\n')
if error_data:
self.fail(error_data)
time.sleep(0.002)
# self.read_until(b'# end\n')
def _do(td: TestData):
self.send(CTRL_D)
td.error_data = b''
td.watchdog_timer = time.time()
td.result = []
try:
_, td.error_data = self.read_until(b'FRAME START\n')
td.watchdog_timer = time.time()
lne, td.error_data = self.read_until(b'\n')
while (
b'FRAME END' not in lne and
not td.error_data and
not self.__class__.exit_event.is_set()
):
td.watchdog_timer = time.time()
td.result.append(lne)
lne, td.error_data = self.read_until(b'\n')
if td.error_data:
return
if self.__class__.exit_event.is_set():
return
except: # NOQA
import traceback
traceback.print_exc()
td.error_data = traceback.format_exc()
return
td.event.set()
test_data = TestData()
t = threading.Thread(
target=_do,
args=(test_data,)
)
t.daemon = True
test_data.watchdog_timer = time.time()
t.start()
while (
(time.time() - test_data.watchdog_timer) * 1000 <= 20000 and
not test_data.event.is_set()
):
test_data.event.wait(0.05)
if not test_data.event.is_set():
self.__class__.exit_event.set()
# self.read_until(REPL_PROMPT)
self.send(CTRL_C)
self.send(CTRL_C)
width = 800
height = 480
if test_data.error_data:
self.fail(test_data.error_data)
try:
frame = bytearray(
b''.join(binascii.unhexlify(lne) for lne in test_data.result)
)
# I don't exactly know why the byte order is backwards but it is
frame = bytes(bytearray([
item for j in range(0, len(frame), 3)
for item in [frame[j + 2], frame[j + 1], frame[j]]
]))
image = Image.new('RGB', (width, height))
image.frombytes(frame)
img = image.convert('RGB')
image.close()
byte_data = list(img.getdata())
img.save(os.path.join(ARTIFACT_PATH, f'frame.png'), 'PNG')
img.close()
with open(os.path.join(ARTIFACT_PATH, f'frame.bin'), 'wb') as f:
# have to flatten the data and remove the alpha
# from the PIL image it is formatted as
# [(r, g, b), (r, g, b)]
f.write(bytes(bytearray([
item for sublist in byte_data
for item in sublist
])))
except binascii.Error:
error = []
for line, err in saved_test_data:
if err:
try:
error.append(err.decode('utf-8'))
except UnicodeDecodeError:
error.append(str(err))
error = '\n'.join(error)
if error:
self.fail(format_error_data(error))
else:
self.fail(b'\n'.join(test_data.result))
except: # NOQA
import traceback
self.fail(traceback.format_exc())
self.assertEqual(res_data, byte_data, 'Frames do not match')
cwd = os.path.abspath(os.getcwd())
ARTIFACT_PATH = os.path.join(cwd, 'artifacts')
MICROPYTHON_PATH = os.path.join(cwd, 'micropython')
if __name__ == '__main__':
import sys
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument(
'--artifact-path',
dest='artifact_path',
help='path to save artifacts to',
action='store'
)
arg_parser.add_argument(
'--mpy-path',
dest='mpy_path',
help='path to micropython',
action='store'
)
arg_parser.add_argument(
'--debug',
dest='debug',
help='debug output',
action='store_true'
)
args = arg_parser.parse_args()
ARTIFACT_PATH = os.path.join(cwd, args.artifact_path)
MICROPYTHON_PATH = os.path.join(cwd, args.mpy_path)
DEBUG = args.debug
if not os.path.exists(ARTIFACT_PATH):
raise RuntimeError(f'Artifact path does not exist ({ARTIFACT_PATH})')
if not os.path.exists(MICROPYTHON_PATH):
raise RuntimeError(f'MicroPython binary not found ({MICROPYTHON_PATH})')
debug_log_path = os.path.join(ARTIFACT_PATH, 'debug.log')
debug_log = open(debug_log_path, 'w')
unittest.main(argv=[sys.argv[0], '-v'])
debug_log.close()
print(f'View the debug output in "{debug_log_path}"')

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,484 @@
import io
import lvgl as lv
import ubinascii
import fs_driver
import sys
if not lv.is_initialized():
lv.init()
try:
lv.log_register_print_cb # NOQA
raise RuntimeError('Logging in LVGL MUST be disabled to run the tests.')
except AttributeError:
pass
except RuntimeError as exc:
buf = io.StringIO()
sys.print_exception(exc, buf)
print('ERROR START')
print(buf.getvalue())
print('ERROR END')
raise
fs_drv = lv.fs_drv_t()
fs_driver.fs_register(fs_drv, 'A')
WIDTH = 800
HEIGHT = 480
def test_func_wrapper(func):
def _wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as exc:
buf = io.StringIO()
sys.print_exception(exc, buf)
print('ERROR START')
print(buf.getvalue())
print('ERROR END')
return _wrapper
CAPTURE_FRAME = False
@test_func_wrapper
def flush(disp, area, color_p):
x1 = area.x1
x2 = area.x2
y1 = area.y1
y2 = area.y2
size = (x2 - x1 + 1) * (y2 - y1 + 1) * lv.color_format_get_size(
disp.get_color_format()
)
data_view = color_p.__dereference__(size)
byte_count = 0
print('FRAME START')
sys.stdout.flush()
while byte_count != size:
chunk_size = min(size - byte_count, 512)
chunk = data_view[byte_count:chunk_size + byte_count]
byte_count += chunk_size
print(ubinascii.hexlify(bytes(chunk)).decode('utf-8'))
sys.stdout.flush()
print('FRAME END')
sys.stdout.flush()
disp.flush_ready()
disp_drv = lv.display_create(WIDTH, HEIGHT)
disp_drv.set_flush_cb(flush)
disp_drv.set_color_format(lv.COLOR_FORMAT.RGB888)
buf = lv.draw_buf_create(WIDTH, HEIGHT, lv.COLOR_FORMAT.RGB888, 0)
color_size = lv.color_format_get_size(disp_drv.get_color_format())
disp_drv.set_draw_buffers(buf, None)
disp_drv.set_render_mode(lv.DISPLAY_RENDER_MODE.FULL)
@test_func_wrapper
def GRID_FR(x):
return lv.COORD.MAX - 100 + x
@test_func_wrapper
def CANVAS_BUF_SIZE(w, h, bpp, stride):
return ((((w * bpp + 7) >> 3) + stride - 1) & ~(
stride - 1)) * h + lv.DRAW_BUF_ALIGN
event_flag = False
@test_func_wrapper
def event_callback(e):
global event_flag
event_flag = True
@test_func_wrapper
def test_event():
scr = lv.screen_active()
scr.add_event_cb(event_callback, lv.EVENT.CLICKED, None)
scr.send_event(lv.EVENT.CLICKED, None)
if not event_flag:
raise RuntimeError('Event failure')
@test_func_wrapper
def create_ui():
# Create a colors
c1 = lv.color_hex(0xff0000)
c2 = lv.palette_darken(lv.PALETTE.BLUE, 2)
c3 = c1.mix(c2, lv.OPA._60)
# Create a style
style_big_font = lv.style_t()
style_big_font.init()
# Try to load as built in font and if not load it using tiny TTF
try:
font_montserrat_24 = lv.font_montserrat_24
except AttributeError:
font_montserrat_24 = lv.tiny_ttf_create_file('A:font_montserrat_24.ttf', 32)
style_big_font.set_text_font(font_montserrat_24)
# Get the active screen
scr = lv.screen_active()
# Declare static array of integers, and test grid setting options
gird_cols = [300, GRID_FR(3), GRID_FR(2), lv.GRID_TEMPLATE_LAST]
gird_rows = [100, GRID_FR(1), lv.GRID_CONTENT, lv.GRID_TEMPLATE_LAST]
scr.set_grid_dsc_array(gird_cols, gird_rows)
chart_type_subject = lv.subject_t()
chart_type_subject.init_int(0)
# Create a widget
dropdown = lv.dropdown(scr)
# Pass a string as argument
dropdown.set_options("Lines\nBars")
# Use grid align options
dropdown.set_grid_cell(
lv.GRID_ALIGN.CENTER,
0,
1,
lv.GRID_ALIGN.CENTER,
0,
1
)
# Bind to a subject
dropdown.bind_value(chart_type_subject)
# Create a chart with an external array of points
chart = lv.chart(lv.screen_active())
chart.set_grid_cell(lv.GRID_ALIGN.STRETCH, 0, 1, lv.GRID_ALIGN.CENTER, 1, 1)
series = chart.add_series(c3, lv.chart.AXIS.PRIMARY_X)
chart_y_array = [10, 25, 50, 40, 30, 35, 60, 65, 70, 75]
chart.set_series_ext_y_array(series, chart_y_array)
# Add custom observer callback
chart_type_subject.add_observer_obj(
lambda _, __: chart_type_observer_cb(chart, chart_type_subject),
chart,
None
)
# Manually set the subject's value
chart_type_subject.set_int(1)
label = lv.label(scr)
label.set_grid_cell(lv.GRID_ALIGN.START, 1, 1, lv.GRID_ALIGN.CENTER, 0, 1)
# Apply styles on main part and default state
label.set_style_bg_opa(lv.OPA._70, 0)
label.set_style_bg_color(c1, 0)
label.set_style_text_color(c2, 0)
label.add_style(style_big_font, 0)
# Declare an array of strings
btnmatrix_options = ["First", "Second", "\n", "Third", ""]
btnmatrix_ctrl = [lv.buttonmatrix.CTRL.DISABLED,
2 | lv.buttonmatrix.CTRL.CHECKED, 1]
btnmatrix = lv.buttonmatrix(scr)
btnmatrix.set_grid_cell(
lv.GRID_ALIGN.STRETCH,
1,
1,
lv.GRID_ALIGN.STRETCH,
1,
1
)
# Pass string and enum arrays
btnmatrix.set_map(btnmatrix_options)
btnmatrix.set_ctrl_map(btnmatrix_ctrl)
# Add style to non main part and non default state
btnmatrix.add_style(style_big_font, lv.PART.ITEMS | lv.STATE.CHECKED)
btnmatrix.set_selected_button(1)
btnmatrix.add_event_cb(
lambda _: buttonmatrix_event_cb(btnmatrix, label),
lv.EVENT.VALUE_CHANGED,
None
)
btnmatrix.send_event(lv.EVENT.VALUE_CHANGED, None)
# Create a base object
cont = lv.obj(scr)
# Span 2 rows
cont.set_grid_cell(lv.GRID_ALIGN.STRETCH, 2, 1, lv.GRID_ALIGN.STRETCH, 0, 2)
# Apply flex layout
cont.set_flex_flow(lv.FLEX_FLOW.COLUMN)
btn1 = list_button_create(cont)
btn2 = list_button_create(cont)
btn3 = list_button_create(cont)
btn4 = list_button_create(cont)
btn5 = list_button_create(cont)
btn6 = list_button_create(cont)
btn7 = list_button_create(cont)
btn8 = list_button_create(cont)
btn9 = list_button_create(cont)
btn10 = list_button_create(cont)
a = lv.anim_t()
a.init()
a.set_var(btn1)
a.set_values(lv.OPA.COVER, lv.OPA._50)
a.set_custom_exec_cb(lambda _, v: opa_anim_cb(btn1, v)) # Pass a callback
a.set_duration(300)
a.set_path_cb(lv.anim_t.path_ease_out)
a.start()
btn2.add_flag(lv.obj.FLAG.HIDDEN)
btn_label = btn3.get_child(0)
btn_label.set_text("A multi-line text with a ° symbol")
btn_label.set_width(lv.pct(100))
a = lv.anim_t()
a.init()
a.set_var(btn4)
a.set_values(lv.OPA.COVER, lv.OPA._50)
a.set_custom_exec_cb(lambda _, v: opa_anim_cb(btn4, v)) # Pass a callback
a.set_path_cb(lv.anim_t.path_ease_out)
a.set_duration(300)
a.set_repeat_count(lv.ANIM_REPEAT_INFINITE)
a.start()
# Wait and delete the button with the animation
cont.get_child(3).delete()
# Large byte array
canvas_buf = bytearray(CANVAS_BUF_SIZE(400, 100, 16, 1))
canvas = lv.canvas(scr)
canvas.set_grid_cell(lv.GRID_ALIGN.START, 0, 2, lv.GRID_ALIGN.START, 2, 1)
# Test RGB565 rendering
canvas.set_buffer(
lv.draw_buf_align(canvas_buf, lv.COLOR_FORMAT.RGB565),
400,
100,
lv.COLOR_FORMAT.RGB565
)
canvas.fill_bg(c2, lv.OPA.COVER)
draw_to_canvas(canvas)
test_img_lvgl_logo_jpg_data = (
b'\xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01\x01\x01\x00\x78\x00\x78\x00\x00\xFF\xE1\x00\x22\x45\x78\x69\x66\x00\x00\x4D\x4D\x00\x2A\x00\x00\x00\x08\x00\x01\x01\x12\x00\x03\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\xFF\xEC\x00\x11\x44\x75\x63\x6B\x79\x00\x01\x00\x04\x00\x00\x00\x64\x00\x00\xFF\xDB\x00\x43\x00\x02\x01\x01\x02\x01\x01\x02\x02\x02\x02\x02\x02\x02\x02\x03\x05\x03\x03\x03\x03\x03\x06\x04\x04\x03\x05\x07\x06\x07\x07\x07\x06\x07\x07\x08\x09\x0B\x09\x08\x08\x0A\x08\x07\x07\x0A\x0D\x0A\x0A\x0B\x0C\x0C\x0C\x0C\x07\x09\x0E\x0F\x0D\x0C\x0E\x0B\x0C\x0C\x0C\xFF\xDB\x00\x43\x01\x02\x02\x02\x03\x03\x03\x06\x03\x03\x06\x0C'
b'\x08\x07\x08\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\xFF\xC0\x00\x11\x08\x00\x28\x00\x69\x03\x01\x22\x00\x02\x11\x01\x03\x11\x01\xFF\xC4\x00\x1F\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\xFF\xC4\x00\xB5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00\x00\x01\x7D\x01\x02\x03\x00\x04\x11\x05\x12\x21\x31\x41\x06\x13\x51\x61\x07\x22\x71\x14\x32\x81\x91\xA1\x08\x23\x42\xB1\xC1\x15\x52\xD1\xF0\x24\x33'
b'\x62\x72\x82\x09\x0A\x16\x17\x18\x19\x1A\x25\x26\x27\x28\x29\x2A\x34\x35\x36\x37\x38\x39\x3A\x43\x44\x45\x46\x47\x48\x49\x4A\x53\x54\x55\x56\x57\x58\x59\x5A\x63\x64\x65\x66\x67\x68\x69\x6A\x73\x74\x75\x76\x77\x78\x79\x7A\x83\x84\x85\x86\x87\x88\x89\x8A\x92\x93\x94\x95\x96\x97\x98\x99\x9A\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFF\xC4\x00\x1F\x01\x00\x03\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A'
b'\x0B\xFF\xC4\x00\xB5\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05\x04\x04\x00\x01\x02\x77\x00\x01\x02\x03\x11\x04\x05\x21\x31\x06\x12\x41\x51\x07\x61\x71\x13\x22\x32\x81\x08\x14\x42\x91\xA1\xB1\xC1\x09\x23\x33\x52\xF0\x15\x62\x72\xD1\x0A\x16\x24\x34\xE1\x25\xF1\x17\x18\x19\x1A\x26\x27\x28\x29\x2A\x35\x36\x37\x38\x39\x3A\x43\x44\x45\x46\x47\x48\x49\x4A\x53\x54\x55\x56\x57\x58\x59\x5A\x63\x64\x65\x66\x67\x68\x69\x6A\x73\x74\x75\x76\x77\x78\x79\x7A\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x92\x93\x94\x95\x96\x97\x98\x99\x9A\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xD2\xD3\xD4'
b'\xD5\xD6\xD7\xD8\xD9\xDA\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFF\xDA\x00\x0C\x03\x01\x00\x02\x11\x03\x11\x00\x3F\x00\xF7\xDF\x8D\x9F\xF0\x75\x95\x9F\x84\xFE\x28\xEB\x5A\x6F\x82\x7E\x11\xA7\x8A\x3C\x33\x61\x72\xF6\xF6\x7A\xAD\xEF\x88\x4D\x84\x97\xEA\x8C\x57\xCD\x10\xAD\xBC\x9B\x51\xB1\x95\xCB\x6E\xDA\x46\x40\x3C\x0E\x5B\xFE\x22\xD2\xD7\xBF\xE8\x82\xE9\x7F\xF8\x58\xBF\xFF\x00\x21\xD7\xC7\xBA\xC7\xFC\x11\x6F\xE3\x65\x9E\xAF\x77\x17\x9B\xF0\xC6\x4F\x2A\x67\x4D\xE9\xE3\xCD\x29\x55\xB0\xC4\x64\x06\x99\x58\x03\xE8\xC0\x1F\x50\x0F\x15\xE1\x5F\xB4\x7F\xEC\xB9\xE3\x5F\xD9\x3F\xC6\xD6\xBA\x0F\x8D\xB4\xB8\x2C'
b'\x6E\xAF\xED\x16\xFE\xC6\x7B\x5B\xD8\x6F\xAC\xEF\xED\xD9\x99\x44\xB0\xCF\x0B\x34\x72\x2E\xE5\x65\x38\x39\x0C\xA4\x10\x0D\x67\x4B\x11\x42\xA3\xE5\xA7\x35\x26\xBB\x34\xFF\x00\x22\xEB\x61\x31\x54\x62\xA7\x5A\x9C\xA2\x9E\xCD\xC5\xA4\xFE\xF4\x7E\x9B\xFF\x00\xC4\x5A\x5A\xF7\xFD\x10\x5D\x2F\xFF\x00\x0B\x17\xFF\x00\xE4\x3A\xF5\x2F\x87\x5F\xF0\x71\x3F\xC4\x6D\x77\xE1\x6C\x7F\x14\x35\xCF\xD9\x87\x5A\xB3\xF8\x3B\x67\xA9\x2E\x9F\xAB\x78\x9F\x4D\xF1\x07\xDA\xC5\x99\x2C\x10\x94\x8D\xED\xE3\x0F\x87\x65\x5C\x96\x54\xDC\x42\x6F\x0C\x40\xAF\xC3\x5A\xF4\xEB\x7F\xDB\x4B\xE2\x86\x95\xFB\x2A\x6A\x1F\x05\xED\xFC\x5D\x7D\x1F\xC3\x3D\x42\xEB\xED\xD3\xE8\xBE'
b'\x54\x45\x1A\x40\xE2\x5D\xA2\x4D\xBE\x60\x8C\xC8\x16\x43\x18\x60\x85\xC6\xEC\x64\x92\x7A\x39\x51\xCC\xA4\xCF\xEB\x13\xC3\x7E\x21\xB3\xF1\x77\x87\xAC\x35\x6D\x3E\x65\xB8\xD3\xF5\x4B\x78\xEE\xED\xA5\x00\x81\x2C\x52\x28\x74\x60\x0F\x3C\xA9\x07\x9F\x5A\xBD\x5C\x3F\xEC\xCB\xFF\x00\x26\xDD\xF0\xF7\xFE\xC5\xAD\x3B\xFF\x00\x49\x63\xAF\x84\xFF\x00\x6B\x2F\xF8\x28\xF7\xC5\x8F\x84\xFF\x00\xF0\x5A\xCF\x86\xFF\x00\x04\xF4\x3D\x5F\x4A\xB7\xF8\x7F\xE2\x49\x34\x91\x7D\x69\x2E\x99\x1C\x93\xB8\x9D\xE4\x12\x81\x29\xF9\xD4\x90\xA3\x04\x1E\x3F\x9E\x68\xD3\x9B\x43\xF4\x92\x8A\xF0\x5F\xF8\x29\xA7\x8F\x7E\x23\x7C\x26\xFD\x89\x7C\x75\xE3\x0F\x85\x7A\x85\xB5'
b'\x87\x8B\xBC\x23\x67\xFD\xB2\x9F\x68\xB2\x4B\xC8\xEE\x2D\x60\x60\xF7\x29\xB1\xC1\x19\xF2\x04\x8C\x08\x19\xCA\x01\xDC\xD7\x9A\xFF\x00\xC1\x11\xFF\x00\x6F\xAD\x73\xFE\x0A\x01\xFB\x20\xCD\xAE\xF8\xBE\xEA\xCA\xE7\xC6\xBE\x1C\xD6\x6E\x34\xAD\x59\xED\xA0\x5B\x75\x99\x4E\xD9\xA0\x94\x46\xBC\x28\x31\x48\x13\x8E\xAD\x13\x1A\x3C\xC7\x7D\x6C\x7D\x89\x45\x7E\x60\xFE\xDB\xDF\xF0\x53\x5F\x8C\x52\xFF\x00\xC1\x5E\x3C\x1F\xFB\x3A\xFC\x1B\xD6\xB4\x8D\x37\x4F\x9D\xF4\xEB\x1D\x6E\x69\xB4\x98\xAF\xA4\x8E\x79\xB7\x5D\x5C\xCA\x0B\xF4\x58\xAC\x8A\x36\xD1\x8F\x99\x5F\x27\xB0\x7F\xFC\x14\x3B\xFE\x0B\x2F\xF1\x2B\xFE\x1A\xD0\xFE\xCF\x7F\xB3\x0F\x86\x2D\x7C\x51'
b'\xE3\xEB\x39\x9A\xCF\x53\xD4\xE6\x81\x6E\x84\x57\x2A\x9B\xE4\x82\x04\x66\x58\x94\x42\x33\xE6\xCD\x31\xD8\xAC\x19\x76\x8D\xA5\x8B\xE5\x17\x32\x3F\x4E\xE8\xAF\xC6\xDF\x1C\x7F\xC1\x4A\xFF\x00\x6E\x3F\xF8\x26\x57\x89\xBC\x3F\xAC\xFE\xD1\x1E\x15\xD0\xFC\x5D\xE0\x2D\x72\xED\x6D\x64\x9A\xDA\x1B\x38\xD9\x58\x82\xE6\x18\xAE\x2C\xC8\x48\xE7\xD8\xAE\xCA\xB3\x21\x57\x08\xD8\x3C\x16\x5F\xA0\xBF\xE0\xB0\x7F\xF0\x54\x5F\x14\x7C\x07\xFD\x88\x3E\x15\xFC\x58\xF8\x1F\xAF\x69\xE9\x67\xF1\x07\x54\x87\xCA\xBA\xBA\xD3\xE3\xBA\xF3\x6D\x25\xB2\x9A\x60\xA5\x24\xCE\xC7\x0C\x80\x30\xEA\xA5\x4A\x9C\x60\x8A\x2C\x1C\xC7\xE8\x85\x15\xC3\xFE\xCC\xFE\x35\xD4\xBE\x24'
b'\xFE\xCE\x1F\x0F\xFC\x45\xAC\x4B\x14\xDA\xBF\x88\x3C\x37\xA7\x6A\x57\xD2\x45\x1F\x94\x92\x4F\x35\xAC\x72\x48\xCA\xA3\x85\x05\x98\x90\x07\x41\x5D\xC5\x22\x8F\xC1\x79\xD7\xF7\xEF\xB4\x7C\xBB\x8E\x31\x5E\x45\xFF\x00\x05\x82\x95\x47\xC3\xEF\xD9\xD5\x37\x0F\x97\xC2\xBA\xA1\xDB\x9E\x99\xD6\x6E\xBB\x7E\x1F\xA5\x7E\x88\x5F\xFC\x5B\x86\xEE\xFA\x69\xA4\xF0\x67\xC3\x93\x24\xB2\x33\xB1\xFF\x00\x84\x53\x4F\x6C\x92\x72\x79\x68\x49\x3F\x89\x26\xB8\x6F\xF8\x28\x37\xC7\xFF\x00\xF8\x46\x7C\x23\xF0\x8E\x13\xE0\x3F\x85\x1A\xB4\x33\xE8\x97\x77\x08\x9A\xB7\x83\x34\xFB\xF5\xB4\x3F\x6D\x96\x32\xB0\xAC\x91\x95\x89\x08\x45\x24\x20\x50\x5B\x24\xE7\x8C\x7E\x63'
b'\xE0\x7F\x00\xE2\x33\x1E\x25\x78\x1C\xBE\xAA\x95\x49\xD2\x9D\x94\x93\x8A\xB2\x70\x93\x77\xF7\xBB\x6D\x6E\xA7\xDB\x78\x81\xF4\x94\xC8\xB8\xC7\x2C\x8E\x5D\x84\xC3\x55\xA7\x2A\x73\x55\x1B\x97\x2B\x4D\x25\x28\x59\x59\xEF\x7A\x89\xFA\x26\x7E\x26\x86\x0D\xDE\x99\x71\xFF\x00\x1E\xF2\x7F\xBA\x6B\xF4\xC6\x5B\xBF\x0D\xFE\xD4\x7F\x01\xBE\x32\x69\xBE\x20\xF8\x6F\xF0\xB7\x4B\x93\xC2\xFE\x03\xD4\x7C\x4D\xA5\x5F\xF8\x77\xC2\x76\x5A\x2D\xFD\x9D\xED\xA1\x89\xA3\x61\x35\xBC\x6A\xCD\x19\xDC\x55\xE3\x6C\xAB\x29\xE8\x08\x04\x7E\x67\x5C\x7F\xC7\xBC\x9F\xEE\x9A\xFD\xD3\x8A\x38\x63\x1B\x90\x63\xDE\x5D\x8F\xE5\xE7\x49\x4B\xDD\x77\x56\x7B\x6A\xD2\xFC\x8F\xCD'
b'\x72\xFC\x7D\x2C\x65\x2F\x6F\x46\xF6\xBD\xB5\xD3\x63\xFA\xEB\xFD\x99\x7F\xE4\xDB\xBE\x1E\xFF\x00\xD8\xB5\xA7\x7F\xE9\x2C\x75\xF9\x41\xFB\x7D\x7F\xCA\xCB\xFF\x00\x06\x7F\xDF\xD0\x7F\xF4\x39\xAB\xF5\x7F\xF6\x65\xFF\x00\x93\x6E\xF8\x7B\xFF\x00\x62\xD6\x9D\xFF\x00\xA4\xB1\xD7\xE6\xDF\xFC\x15\x13\xFE\x09\xD7\xFB\x48\x7C\x51\xFF\x00\x82\x9C\xE8\x9F\x1B\x3E\x0D\x68\x3A\x1D\xE2\xF8\x67\x4F\xD3\xDB\x4E\xBB\xBE\xD4\xED\x63\x51\x75\x01\x90\x9D\xD0\xCA\xC0\x90\x37\x0E\xD8\x3F\xCB\xE6\x62\x7A\x2F\x64\x7E\xA9\xEB\x1A\x4D\xAF\x88\x34\x8B\xAB\x0B\xE8\x63\xBA\xB3\xBE\x85\xED\xEE\x21\x90\x65\x25\x8D\xC1\x56\x52\x3D\x08\x24\x7E\x35\xF8\xAD\xFF\x00\x04'
b'\x6B\xF1\x62\xFF\x00\xC1\x36\x3F\xE0\xA8\xFF\x00\x1D\x3E\x06\xF8\x96\xEE\x4B\x5D\x02\x4B\x4B\xC9\xED\xA5\x94\xF2\xE3\x4D\x0F\x77\x6F\x36\x3F\xDB\xD3\xE4\x9A\x43\xDF\xE5\x1F\x87\xD1\xDF\x02\xB5\x0F\xF8\x29\x34\x9F\x1B\xBC\x1E\xBE\x3A\xB1\xF8\x7E\x9E\x09\x6D\x6A\xCC\x78\x81\xA0\x3A\x77\x9A\xBA\x7F\x9C\xBF\x69\xD9\xB1\xCB\x6E\xF2\xB7\xE3\x6F\x39\xC5\x70\xBF\xF0\x5B\xBF\xF8\x24\x07\xC5\x5F\xDA\x9F\xF6\xAB\xD0\xFE\x26\x7C\x19\xD3\xED\x27\xBC\xD4\xB4\x33\xA6\x6B\xE7\xFB\x56\x2D\x3A\x55\x96\x2D\xD1\xA4\x9B\x9D\x94\xB8\x92\xDE\x5F\x29\x80\xCF\xCB\x0E\x0F\x0D\xCD\x79\x03\xBE\xE8\xE0\xBF\xE0\xDF\x2F\x05\x5F\x7E\xD8\x1F\xF0\x50\x1F\x8D\x1F\xB4'
b'\xA7\x88\x2D\x9D\xBC\xAB\x8B\x84\xD3\xFC\xCE\x44\x57\x7A\x84\xAD\x23\x2A\x1F\xFA\x63\x6A\x82\x2C\x76\x59\xC5\x56\xFF\x00\x83\x7A\x7E\xCF\x2F\xFC\x15\x27\xF6\x88\x7D\x7B\x6F\xFC\x26\x3E\x4E\xA2\x57\xCE\xFF\x00\x5D\x83\xAB\x7F\xA5\xE3\x3C\xFF\x00\xAC\xF2\x73\xDF\xA5\x7E\x80\x7F\xC1\x1E\xBF\x62\x6D\x43\xF6\x0C\xFD\x87\x74\x1F\x07\xEB\xF6\xD6\xB6\xDE\x2F\xD4\x2E\xAE\x35\x8F\x10\x2D\xBC\xAB\x32\x0B\xA9\x9F\x0A\x81\xD7\x86\xD9\x04\x70\x46\x48\x24\x12\x84\x8E\x0D\x7C\xBF\xFF\x00\x05\x05\xFF\x00\x82\x37\x7C\x52\xD1\x7F\x6B\xD9\x3F\x68\x6F\xD9\x6F\xC4\x56\xBA\x0F\x8D\xB5\x09\xDA\xF7\x53\xD1\xE5\x9D\x2D\x4B\x5D\x3A\xED\x9A\x68\x1E\x40\x61\x75'
b'\x9F\xAC\x90\xCC\x02\x96\x2E\xDB\x8E\xED\xAA\x6E\x2E\x5D\x0D\x4F\xF8\x29\xF7\xFC\x14\x57\xE3\xDF\xEC\xD9\xE2\x4F\x1B\xAE\xA1\xFB\x38\xF8\x4F\xC5\x9F\x06\x3C\x37\x79\x6C\x2D\xFC\x41\xAE\x23\x4F\x6B\x72\x1F\xCA\x58\xE4\x64\x2C\x57\x3E\x7C\xBB\x01\x0B\xC1\xC7\xD6\xBE\x70\xFF\x00\x82\xC0\x7C\x76\x9B\xF6\x99\xFF\x00\x82\x2B\x7E\xCF\x1E\x39\x9F\xC3\x7A\x1F\x84\x5F\x5E\xF1\x44\xF2\x2E\x91\xA3\xC6\x63\xB1\xB3\x44\x8F\x50\x89\x04\x4A\x40\xC0\x2A\x81\xB1\x8C\x65\x8F\x5E\xB5\xD7\xF8\xF7\xF6\x08\xFD\xBC\xFF\x00\xE0\xA7\x5A\x86\x8B\xE1\x6F\x8F\x3A\xD6\x85\xE0\x3F\x87\x9A\x7D\xE2\x5C\xDD\xA4\x12\x59\x37\x9A\xCB\x91\xE6\xAC\x16\x8C\xE6\x69\x40\x27'
b'\x68\x95\xD2\x30\x4E\x46\x3B\xFB\xDF\xFC\x16\x0F\xFE\x09\x79\xE2\xAF\x8E\x1F\xB0\xDF\xC2\x8F\x84\xBF\x04\x74\x0B\x5B\xCB\x4F\x87\xBA\x9C\x21\x2D\xEE\xF5\x18\xAD\x8C\x76\x91\x59\x4D\x00\x76\x79\x0A\x87\x72\xCC\xA5\x8F\x52\x58\x9C\x75\xA3\x40\xD4\xFB\x27\xF6\x2E\xFF\x00\x93\x3A\xF8\x4D\xFF\x00\x62\x6E\x8F\xFF\x00\xA4\x30\xD7\xA6\x57\xE4\xD7\x81\x3C\x3B\xFF\x00\x05\x42\xF8\x6D\xE0\x5D\x17\xC3\xBA\x4E\x97\xF0\xEA\x1D\x2F\xC3\xF6\x10\x69\xB6\x68\xF2\xE9\x6E\xC9\x0C\x31\xAC\x68\x0B\x17\xE4\xED\x51\xCF\x7A\xDE\xFE\xD2\xFF\x00\x82\xA6\xFF\x00\xD0\x3F\xE1\x7F\xFD\xF5\xA7\x7F\xF1\x74\xAC\x57\x31\xF4\xAF\x8C\xBF\xE0\x99\xCD\xA8\x78\x92\xEA\x7D'
b'\x1B\xC4\x96\xF6\x7A\x6C\xAE\x5E\x18\x27\xB4\x69\x24\x88\x1F\xE1\x2C\x18\x67\x1E\xB8\x19\xAE\x23\xF6\xA8\xFF\x00\x82\x42\x6A\xDF\xB4\x2E\x89\xE0\x6B\x7B\x4F\x1C\x69\xFA\x64\xBE\x13\xD3\x67\xD3\xE6\x33\x69\xAF\x22\xDC\x6F\xB8\x79\xC3\xAE\x1C\x15\xC7\x98\x54\x83\x9E\x80\xF1\xD2\x8A\x29\xF0\x9C\xDF\x0D\x66\x6B\x38\xC9\xBF\x77\x59\x29\x46\xFF\x00\x12\xB4\xB7\x5C\xB2\xBA\xFC\x34\xE8\x7C\xCD\x1E\x0D\xCA\x29\x4E\x73\xA7\x4A\xCE\x6A\xCF\xDE\x96\xD7\x4F\x45\x7D\x35\x4B\x6F\x43\x92\xF8\x57\xFF\x00\x04\x37\xD6\x7C\x09\xE1\x1F\x89\x5A\x65\xD7\xC4\x4D\x36\xE1\xBC\x75\xE0\xCD\x47\xC2\xD0\x49\x0E\x94\xEB\xF6\x49\x2E\x42\x6D\x99\x81\x93\xE6\x55\xD9'
b'\xCA\x8C\x13\x9E\xA2\xBE\x73\xF0\x47\xFC\x1A\x7B\xAA\xAF\x8B\x2C\x1B\xC4\xDF\x18\xB4\xF9\xB4\x15\x94\x1B\xE8\xB4\xCD\x0D\xE3\xBB\x96\x2F\xE2\x58\xDE\x49\x59\x11\x88\xE3\x73\x2B\x01\xD7\x69\xE9\x45\x15\xED\x71\x07\x12\x66\x19\xDE\x2F\xEB\xF9\x94\xF9\xAA\x34\x95\xEC\x96\x8B\x6D\x12\x48\xF7\x30\x79\x7D\x0C\x2D\x3F\x65\x41\x59\x6F\xBB\x7F\x99\xFB\x29\xE1\x9F\x0D\xD9\xF8\x3F\xC3\x7A\x7E\x93\xA7\xC3\xF6\x7D\x3F\x4B\xB6\x8E\xD2\xDA\x20\x49\xF2\xE2\x8D\x42\x22\xE4\xF3\xC2\x80\x39\xAB\xD4\x51\x5E\x19\xD8\x14\x51\x45\x00\x14\x51\x45\x00\x14\x51\x45\x00\x14\x51\x45\x00\x7F\xFF\xD9'
)
test_img_lvgl_logo_jpg = lv.image_dsc_t(
dict(
header=dict(cf=lv.COLOR_FORMAT.RAW_ALPHA, w=105, h=40),
data_size=2864,
data=test_img_lvgl_logo_jpg_data
)
)
img = lv.image(scr)
img.set_src(test_img_lvgl_logo_jpg)
img.align(lv.ALIGN.BOTTOM_RIGHT, -20, -20)
img.add_flag(lv.obj.FLAG.IGNORE_LAYOUT)
img = lv.image(scr)
img.set_src("A:test_img_lvgl_logo.png")
img.set_pos(500, 420)
img.add_flag(lv.obj.FLAG.IGNORE_LAYOUT)
img.set_rotation(200)
img.set_scale_x(400)
@test_func_wrapper
def chart_type_observer_cb(chart, subject):
v = subject.get_int()
# chart = observer.get_target()
chart.set_type(lv.chart.TYPE.LINE if v == 0 else lv.chart.TYPE.BAR)
@test_func_wrapper
def buttonmatrix_event_cb(buttonmatrix, label):
# label = e.get_user_data()
# buttonmatrix = e.get_target()
idx = buttonmatrix.get_selected_button()
text = buttonmatrix.get_button_text(idx)
label.set_text(text)
@test_func_wrapper
def list_button_create(parent):
btn = lv.button(parent)
btn.set_size(lv.pct(100), lv.SIZE_CONTENT)
# Get an integer
idx = btn.get_index()
# Formatted string for label
label = lv.label(btn)
label.set_text(lv.SYMBOL.FILE + " Item %d" % idx)
return btn
@test_func_wrapper
def opa_anim_cb(button, value):
try:
button.set_style_opa(value, 0)
except: # NOQA
pass
@test_func_wrapper
def draw_to_canvas(canvas):
layer = lv.layer_t()
canvas.init_layer(layer)
# Use draw descriptors
test_img_lvgl_logo_png_data = (
b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x69\x00\x00\x00\x28\x08\x06\x00\x00\x00\xFD\x86\xD4\xF3\x00\x00\x00\x04\x67\x41\x4D\x41\x00\x00\xB1\x8F\x0B\xFC\x61\x05\x00\x00\x0A\x49\x69\x43\x43\x50\x73\x52\x47\x42\x20\x49\x45\x43\x36\x31\x39\x36\x36\x2D\x32\x2E\x31\x00\x00\x48\x89\x9D\x53\x77\x58\x93\xF7\x16\x3E\xDF\xF7\x65\x0F\x56\x42\xD8\xF0\xB1\x97\x6C\x81\x00\x22\x23\xAC\x08\xC8\x10\x59\xA2\x10\x92\x00\x61\x84\x10\x12\x40\xC5\x85\x88\x0A\x56\x14\x15\x11\x9C\x48\x55\xC4\x82\xD5\x0A\x48\x9D\x88\xE2\xA0\x28\xB8\x67\x41\x8A\x88\x5A\x8B\x55\x5C\x38\xEE\x1F\xDC\xA7\xB5\x7D\x7A\xEF\xED\xED\xFB\xD7\xFB\xBC'
b'\xE7\x9C\xE7\xFC\xCE\x79\xCF\x0F\x80\x11\x12\x26\x91\xE6\xA2\x6A\x00\x39\x52\x85\x3C\x3A\xD8\x1F\x8F\x4F\x48\xC4\xC9\xBD\x80\x02\x15\x48\xE0\x04\x20\x10\xE6\xCB\xC2\x67\x05\xC5\x00\x00\xF0\x03\x79\x78\x7E\x74\xB0\x3F\xFC\x01\xAF\x6F\x00\x02\x00\x70\xD5\x2E\x24\x12\xC7\xE1\xFF\x83\xBA\x50\x26\x57\x00\x20\x91\x00\xE0\x22\x12\xE7\x0B\x01\x90\x52\x00\xC8\x2E\x54\xC8\x14\x00\xC8\x18\x00\xB0\x53\xB3\x64\x0A\x00\x94\x00\x00\x6C\x79\x7C\x42\x22\x00\xAA\x0D\x00\xEC\xF4\x49\x3E\x05\x00\xD8\xA9\x93\xDC\x17\x00\xD8\xA2\x1C\xA9\x08\x00\x8D\x01\x00\x99\x28\x47\x24\x02\x40\xBB\x00\x60\x55\x81\x52\x2C\x02\xC0\xC2\x00\xA0\xAC\x40\x22\x2E\x04\xC0\xAE'
b'\x01\x80\x59\xB6\x32\x47\x02\x80\xBD\x05\x00\x76\x8E\x58\x90\x0F\x40\x60\x00\x80\x99\x42\x2C\xCC\x00\x20\x38\x02\x00\x43\x1E\x13\xCD\x03\x20\x4C\x03\xA0\x30\xD2\xBF\xE0\xA9\x5F\x70\x85\xB8\x48\x01\x00\xC0\xCB\x95\xCD\x97\x4B\xD2\x33\x14\xB8\x95\xD0\x1A\x77\xF2\xF0\xE0\xE2\x21\xE2\xC2\x6C\xB1\x42\x61\x17\x29\x10\x66\x09\xE4\x22\x9C\x97\x9B\x23\x13\x48\xE7\x03\x4C\xCE\x0C\x00\x00\x1A\xF9\xD1\xC1\xFE\x38\x3F\x90\xE7\xE6\xE4\xE1\xE6\x66\xE7\x6C\xEF\xF4\xC5\xA2\xFE\x6B\xF0\x6F\x22\x3E\x21\xF1\xDF\xFE\xBC\x8C\x02\x04\x00\x10\x4E\xCF\xEF\xDA\x5F\xE5\xE5\xD6\x03\x70\xC7\x01\xB0\x75\xBF\x6B\xA9\x5B\x00\xDA\x56\x00\x68\xDF\xF9\x5D\x33\xDB\x09'
b'\xA0\x5A\x0A\xD0\x7A\xF9\x8B\x79\x38\xFC\x40\x1E\x9E\xA1\x50\xC8\x3C\x1D\x1C\x0A\x0B\x0B\xED\x25\x62\xA1\xBD\x30\xE3\x8B\x3E\xFF\x33\xE1\x6F\xE0\x8B\x7E\xF6\xFC\x40\x1E\xFE\xDB\x7A\xF0\x00\x71\x9A\x40\x99\xAD\xC0\xA3\x83\xFD\x71\x61\x6E\x76\xAE\x52\x8E\xE7\xCB\x04\x42\x31\x6E\xF7\xE7\x23\xFE\xC7\x85\x7F\xFD\x8E\x29\xD1\xE2\x34\xB1\x5C\x2C\x15\x8A\xF1\x58\x89\xB8\x50\x22\x4D\xC7\x79\xB9\x52\x91\x44\x21\xC9\x95\xE2\x12\xE9\x7F\x32\xF1\x1F\x96\xFD\x09\x93\x77\x0D\x00\xAC\x86\x4F\xC0\x4E\xB6\x07\xB5\xCB\x6C\xC0\x7E\xEE\x01\x02\x8B\x0E\x58\xD2\x76\x00\x40\x7E\xF3\x2D\x8C\x1A\x0B\x91\x00\x10\x67\x34\x32\x79\xF7\x00\x00\x93\xBF\xF9\x8F\x40'
b'\x2B\x01\x00\xCD\x97\xA4\xE3\x00\x00\xBC\xE8\x18\x5C\xA8\x94\x17\x4C\xC6\x08\x00\x00\x44\xA0\x81\x2A\xB0\x41\x07\x0C\xC1\x14\xAC\xC0\x0E\x9C\xC1\x1D\xBC\xC0\x17\x02\x61\x06\x44\x40\x0C\x24\xC0\x3C\x10\x42\x06\xE4\x80\x1C\x0A\xA1\x18\x96\x41\x19\x54\xC0\x3A\xD8\x04\xB5\xB0\x03\x1A\xA0\x11\x9A\xE1\x10\xB4\xC1\x31\x38\x0D\xE7\xE0\x12\x5C\x81\xEB\x70\x17\x06\x60\x18\x9E\xC2\x18\xBC\x86\x09\x04\x41\xC8\x08\x13\x61\x21\x3A\x88\x11\x62\x8E\xD8\x22\xCE\x08\x17\x99\x8E\x04\x22\x61\x48\x34\x92\x80\xA4\x20\xE9\x88\x14\x51\x22\xC5\xC8\x72\xA4\x02\xA9\x42\x6A\x91\x5D\x48\x23\xF2\x2D\x72\x14\x39\x8D\x5C\x40\xFA\x90\xDB\xC8\x20\x32\x8A\xFC\x8A\xBC'
b'\x47\x31\x94\x81\xB2\x51\x03\xD4\x02\x75\x40\xB9\xA8\x1F\x1A\x8A\xC6\xA0\x73\xD1\x74\x34\x0F\x5D\x80\x96\xA2\x6B\xD1\x1A\xB4\x1E\x3D\x80\xB6\xA2\xA7\xD1\x4B\xE8\x75\x74\x00\x7D\x8A\x8E\x63\x80\xD1\x31\x0E\x66\x8C\xD9\x61\x5C\x8C\x87\x45\x60\x89\x58\x1A\x26\xC7\x16\x63\xE5\x58\x35\x56\x8F\x35\x63\x1D\x58\x37\x76\x15\x1B\xC0\x9E\x61\xEF\x08\x24\x02\x8B\x80\x13\xEC\x08\x5E\x84\x10\xC2\x6C\x82\x90\x90\x47\x58\x4C\x58\x43\xA8\x25\xEC\x23\xB4\x12\xBA\x08\x57\x09\x83\x84\x31\xC2\x27\x22\x93\xA8\x4F\xB4\x25\x7A\x12\xF9\xC4\x78\x62\x3A\xB1\x90\x58\x46\xAC\x26\xEE\x21\x1E\x21\x9E\x25\x5E\x27\x0E\x13\x5F\x93\x48\x24\x0E\xC9\x92\xE4\x4E\x0A\x21'
b'\x25\x90\x32\x49\x0B\x49\x6B\x48\xDB\x48\x2D\xA4\x53\xA4\x3E\xD2\x10\x69\x9C\x4C\x26\xEB\x90\x6D\xC9\xDE\xE4\x08\xB2\x80\xAC\x20\x97\x91\xB7\x90\x0F\x90\x4F\x92\xFB\xC9\xC3\xE4\xB7\x14\x3A\xC5\x88\xE2\x4C\x09\xA2\x24\x52\xA4\x94\x12\x4A\x35\x65\x3F\xE5\x04\xA5\x9F\x32\x42\x99\xA0\xAA\x51\xCD\xA9\x9E\xD4\x08\xAA\x88\x3A\x9F\x5A\x49\x6D\xA0\x76\x50\x2F\x53\x87\xA9\x13\x34\x75\x9A\x25\xCD\x9B\x16\x43\xCB\xA4\x2D\xA3\xD5\xD0\x9A\x69\x67\x69\xF7\x68\x2F\xE9\x74\xBA\x09\xDD\x83\x1E\x45\x97\xD0\x97\xD2\x6B\xE8\x07\xE9\xE7\xE9\x83\xF4\x77\x0C\x0D\x86\x0D\x83\xC7\x48\x62\x28\x19\x6B\x19\x7B\x19\xA7\x18\xB7\x19\x2F\x99\x4C\xA6\x05\xD3\x97\x99'
b'\xC8\x54\x30\xD7\x32\x1B\x99\x67\x98\x0F\x98\x6F\x55\x58\x2A\xF6\x2A\x7C\x15\x91\xCA\x12\x95\x3A\x95\x56\x95\x7E\x95\xE7\xAA\x54\x55\x73\x55\x3F\xD5\x79\xAA\x0B\x54\xAB\x55\x0F\xAB\x5E\x56\x7D\xA6\x46\x55\xB3\x50\xE3\xA9\x09\xD4\x16\xAB\xD5\xA9\x1D\x55\xBB\xA9\x36\xAE\xCE\x52\x77\x52\x8F\x50\xCF\x51\x5F\xA3\xBE\x5F\xFD\x82\xFA\x63\x0D\xB2\x86\x85\x46\xA0\x86\x48\xA3\x54\x63\xB7\xC6\x19\x8D\x21\x16\xC6\x32\x65\xF1\x58\x42\xD6\x72\x56\x03\xEB\x2C\x6B\x98\x4D\x62\x5B\xB2\xF9\xEC\x4C\x76\x05\xFB\x1B\x76\x2F\x7B\x4C\x53\x43\x73\xAA\x66\xAC\x66\x91\x66\x9D\xE6\x71\xCD\x01\x0E\xC6\xB1\xE0\xF0\x39\xD9\x9C\x4A\xCE\x21\xCE\x0D\xCE\x7B\x2D\x03'
b'\x2D\x3F\x2D\xB1\xD6\x6A\xAD\x66\xAD\x7E\xAD\x37\xDA\x7A\xDA\xBE\xDA\x62\xED\x72\xED\x16\xED\xEB\xDA\xEF\x75\x70\x9D\x40\x9D\x2C\x9D\xF5\x3A\x6D\x3A\xF7\x75\x09\xBA\x36\xBA\x51\xBA\x85\xBA\xDB\x75\xCF\xEA\x3E\xD3\x63\xEB\x79\xE9\x09\xF5\xCA\xF5\x0E\xE9\xDD\xD1\x47\xF5\x6D\xF4\xA3\xF5\x17\xEA\xEF\xD6\xEF\xD1\x1F\x37\x30\x34\x08\x36\x90\x19\x6C\x31\x38\x63\xF0\xCC\x90\x63\xE8\x6B\x98\x69\xB8\xD1\xF0\x84\xE1\xA8\x11\xCB\x68\xBA\x91\xC4\x68\xA3\xD1\x49\xA3\x27\xB8\x26\xEE\x87\x67\xE3\x35\x78\x17\x3E\x66\xAC\x6F\x1C\x62\xAC\x34\xDE\x65\xDC\x6B\x3C\x61\x62\x69\x32\xDB\xA4\xC4\xA4\xC5\xE4\xBE\x29\xCD\x94\x6B\x9A\x66\xBA\xD1\xB4\xD3\x74\xCC'
b'\xCC\xC8\x2C\xDC\xAC\xD8\xAC\xC9\xEC\x8E\x39\xD5\x9C\x6B\x9E\x61\xBE\xD9\xBC\xDB\xFC\x8D\x85\xA5\x45\x9C\xC5\x4A\x8B\x36\x8B\xC7\x96\xDA\x96\x7C\xCB\x05\x96\x4D\x96\xF7\xAC\x98\x56\x3E\x56\x79\x56\xF5\x56\xD7\xAC\x49\xD6\x5C\xEB\x2C\xEB\x6D\xD6\x57\x6C\x50\x1B\x57\x9B\x0C\x9B\x3A\x9B\xCB\xB6\xA8\xAD\x9B\xAD\xC4\x76\x9B\x6D\xDF\x14\xE2\x14\x8F\x29\xD2\x29\xF5\x53\x6E\xDA\x31\xEC\xFC\xEC\x0A\xEC\x9A\xEC\x06\xED\x39\xF6\x61\xF6\x25\xF6\x6D\xF6\xCF\x1D\xCC\x1C\x12\x1D\xD6\x3B\x74\x3B\x7C\x72\x74\x75\xCC\x76\x6C\x70\xBC\xEB\xA4\xE1\x34\xC3\xA9\xC4\xA9\xC3\xE9\x57\x67\x1B\x67\xA1\x73\x9D\xF3\x35\x17\xA6\x4B\x90\xCB\x12\x97\x76\x97\x17\x53'
b'\x6D\xA7\x8A\xA7\x6E\x9F\x7A\xCB\x95\xE5\x1A\xEE\xBA\xD2\xB5\xD3\xF5\xA3\x9B\xBB\x9B\xDC\xAD\xD9\x6D\xD4\xDD\xCC\x3D\xC5\x7D\xAB\xFB\x4D\x2E\x9B\x1B\xC9\x5D\xC3\x3D\xEF\x41\xF4\xF0\xF7\x58\xE2\x71\xCC\xE3\x9D\xA7\x9B\xA7\xC2\xF3\x90\xE7\x2F\x5E\x76\x5E\x59\x5E\xFB\xBD\x1E\x4F\xB3\x9C\x26\x9E\xD6\x30\x6D\xC8\xDB\xC4\x5B\xE0\xBD\xCB\x7B\x60\x3A\x3E\x3D\x65\xFA\xCE\xE9\x03\x3E\xC6\x3E\x02\x9F\x7A\x9F\x87\xBE\xA6\xBE\x22\xDF\x3D\xBE\x23\x7E\xD6\x7E\x99\x7E\x07\xFC\x9E\xFB\x3B\xFA\xCB\xFD\x8F\xF8\xBF\xE1\x79\xF2\x16\xF1\x4E\x05\x60\x01\xC1\x01\xE5\x01\xBD\x81\x1A\x81\xB3\x03\x6B\x03\x1F\x04\x99\x04\xA5\x07\x35\x05\x8D\x05\xBB\x06\x2F\x0C'
b'\x3E\x15\x42\x0C\x09\x0D\x59\x1F\x72\x93\x6F\xC0\x17\xF2\x1B\xF9\x63\x33\xDC\x67\x2C\x9A\xD1\x15\xCA\x08\x9D\x15\x5A\x1B\xFA\x30\xCC\x26\x4C\x1E\xD6\x11\x8E\x86\xCF\x08\xDF\x10\x7E\x6F\xA6\xF9\x4C\xE9\xCC\xB6\x08\x88\xE0\x47\x6C\x88\xB8\x1F\x69\x19\x99\x17\xF9\x7D\x14\x29\x2A\x32\xAA\x2E\xEA\x51\xB4\x53\x74\x71\x74\xF7\x2C\xD6\xAC\xE4\x59\xFB\x67\xBD\x8E\xF1\x8F\xA9\x8C\xB9\x3B\xDB\x6A\xB6\x72\x76\x67\xAC\x6A\x6C\x52\x6C\x63\xEC\x9B\xB8\x80\xB8\xAA\xB8\x81\x78\x87\xF8\x45\xF1\x97\x12\x74\x13\x24\x09\xED\x89\xE4\xC4\xD8\xC4\x3D\x89\xE3\x73\x02\xE7\x6C\x9A\x33\x9C\xE4\x9A\x54\x96\x74\x63\xAE\xE5\xDC\xA2\xB9\x17\xE6\xE9\xCE\xCB\x9E\x77'
b'\x3C\x59\x35\x59\x90\x7C\x38\x85\x98\x12\x97\xB2\x3F\xE5\x83\x20\x42\x50\x2F\x18\x4F\xE5\xA7\x6E\x4D\x1D\x13\xF2\x84\x9B\x85\x4F\x45\xBE\xA2\x8D\xA2\x51\xB1\xB7\xB8\x4A\x3C\x92\xE6\x9D\x56\x95\xF6\x38\xDD\x3B\x7D\x43\xFA\x68\x86\x4F\x46\x75\xC6\x33\x09\x4F\x52\x2B\x79\x91\x19\x92\xB9\x23\xF3\x4D\x56\x44\xD6\xDE\xAC\xCF\xD9\x71\xD9\x2D\x39\x94\x9C\x94\x9C\xA3\x52\x0D\x69\x96\xB4\x2B\xD7\x30\xB7\x28\xB7\x4F\x66\x2B\x2B\x93\x0D\xE4\x79\xE6\x6D\xCA\x1B\x93\x87\xCA\xF7\xE4\x23\xF9\x73\xF3\xDB\x15\x6C\x85\x4C\xD1\xA3\xB4\x52\xAE\x50\x0E\x16\x4C\x2F\xA8\x2B\x78\x5B\x18\x5B\x78\xB8\x48\xBD\x48\x5A\xD4\x33\xDF\x66\xFE\xEA\xF9\x23\x0B\x82\x16'
b'\x7C\xBD\x90\xB0\x50\xB8\xB0\xB3\xD8\xB8\x78\x59\xF1\xE0\x22\xBF\x45\xBB\x16\x23\x8B\x53\x17\x77\x2E\x31\x5D\x52\xBA\x64\x78\x69\xF0\xD2\x7D\xCB\x68\xCB\xB2\x96\xFD\x50\xE2\x58\x52\x55\xF2\x6A\x79\xDC\xF2\x8E\x52\x83\xD2\xA5\xA5\x43\x2B\x82\x57\x34\x95\xA9\x94\xC9\xCB\x6E\xAE\xF4\x5A\xB9\x63\x15\x61\x95\x64\x55\xEF\x6A\x97\xD5\x5B\x56\x7F\x2A\x17\x95\x5F\xAC\x70\xAC\xA8\xAE\xF8\xB0\x46\xB8\xE6\xE2\x57\x4E\x5F\xD5\x7C\xF5\x79\x6D\xDA\xDA\xDE\x4A\xB7\xCA\xED\xEB\x48\xEB\xA4\xEB\x6E\xAC\xF7\x59\xBF\xAF\x4A\xBD\x6A\x41\xD5\xD0\x86\xF0\x0D\xAD\x1B\xF1\x8D\xE5\x1B\x5F\x6D\x4A\xDE\x74\xA1\x7A\x6A\xF5\x8E\xCD\xB4\xCD\xCA\xCD\x03\x35\x61\x35'
b'\xED\x5B\xCC\xB6\xAC\xDB\xF2\xA1\x36\xA3\xF6\x7A\x9D\x7F\x5D\xCB\x56\xFD\xAD\xAB\xB7\xBE\xD9\x26\xDA\xD6\xBF\xDD\x77\x7B\xF3\x0E\x83\x1D\x15\x3B\xDE\xEF\x94\xEC\xBC\xB5\x2B\x78\x57\x6B\xBD\x45\x7D\xF5\x6E\xD2\xEE\x82\xDD\x8F\x1A\x62\x1B\xBA\xBF\xE6\x7E\xDD\xB8\x47\x77\x4F\xC5\x9E\x8F\x7B\xA5\x7B\x07\xF6\x45\xEF\xEB\x6A\x74\x6F\x6C\xDC\xAF\xBF\xBF\xB2\x09\x6D\x52\x36\x8D\x1E\x48\x3A\x70\xE5\x9B\x80\x6F\xDA\x9B\xED\x9A\x77\xB5\x70\x5A\x2A\x0E\xC2\x41\xE5\xC1\x27\xDF\xA6\x7C\x7B\xE3\x50\xE8\xA1\xCE\xC3\xDC\xC3\xCD\xDF\x99\x7F\xB7\xF5\x08\xEB\x48\x79\x2B\xD2\x3A\xBF\x75\xAC\x2D\xA3\x6D\xA0\x3D\xA1\xBD\xEF\xE8\x8C\xA3\x9D\x1D\x5E\x1D\x47'
b'\xBE\xB7\xFF\x7E\xEF\x31\xE3\x63\x75\xC7\x35\x8F\x57\x9E\xA0\x9D\x28\x3D\xF1\xF9\xE4\x82\x93\xE3\xA7\x64\xA7\x9E\x9D\x4E\x3F\x3D\xD4\x99\xDC\x79\xF7\x4C\xFC\x99\x6B\x5D\x51\x5D\xBD\x67\x43\xCF\x9E\x3F\x17\x74\xEE\x4C\xB7\x5F\xF7\xC9\xF3\xDE\xE7\x8F\x5D\xF0\xBC\x70\xF4\x22\xF7\x62\xDB\x25\xB7\x4B\xAD\x3D\xAE\x3D\x47\x7E\x70\xFD\xE1\x48\xAF\x5B\x6F\xEB\x65\xF7\xCB\xED\x57\x3C\xAE\x74\xF4\x4D\xEB\x3B\xD1\xEF\xD3\x7F\xFA\x6A\xC0\xD5\x73\xD7\xF8\xD7\x2E\x5D\x9F\x79\xBD\xEF\xC6\xEC\x1B\xB7\x6E\x26\xDD\x1C\xB8\x25\xBA\xF5\xF8\x76\xF6\xED\x17\x77\x0A\xEE\x4C\xDC\x5D\x7A\x8F\x78\xAF\xFC\xBE\xDA\xFD\xEA\x07\xFA\x0F\xEA\x7F\xB4\xFE\xB1\x65\xC0'
b'\x6D\xE0\xF8\x60\xC0\x60\xCF\xC3\x59\x0F\xEF\x0E\x09\x87\x9E\xFE\x94\xFF\xD3\x87\xE1\xD2\x47\xCC\x47\xD5\x23\x46\x23\x8D\x8F\x9D\x1F\x1F\x1B\x0D\x1A\xBD\xF2\x64\xCE\x93\xE1\xA7\xB2\xA7\x13\xCF\xCA\x7E\x56\xFF\x79\xEB\x73\xAB\xE7\xDF\xFD\xE2\xFB\x4B\xCF\x58\xFC\xD8\xF0\x0B\xF9\x8B\xCF\xBF\xAE\x79\xA9\xF3\x72\xEF\xAB\xA9\xAF\x3A\xC7\x23\xC7\x1F\xBC\xCE\x79\x3D\xF1\xA6\xFC\xAD\xCE\xDB\x7D\xEF\xB8\xEF\xBA\xDF\xC7\xBD\x1F\x99\x28\xFC\x40\xFE\x50\xF3\xD1\xFA\x63\xC7\xA7\xD0\x4F\xF7\x3E\xE7\x7C\xFE\xFC\x2F\xF7\x84\xF3\xFB\x2D\x47\x38\xCF\x00\x00\x00\x20\x63\x48\x52\x4D\x00\x00\x7A\x26\x00\x00\x80\x84\x00\x00\xFA\x00\x00\x00\x80\xE8\x00\x00'
b'\x75\x30\x00\x00\xEA\x60\x00\x00\x3A\x98\x00\x00\x17\x70\x9C\xBA\x51\x3C\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0B\x13\x00\x00\x0B\x13\x01\x00\x9A\x9C\x18\x00\x00\x0F\xFF\x49\x44\x41\x54\x78\x9C\xED\x5B\x7B\xAC\x66\x55\x75\xFF\xAD\xB5\x1F\xE7\x7C\xDF\x77\x5F\xF3\x9E\x61\x06\x94\x97\x26\x35\x28\x68\x7C\x57\x48\x5B\x6A\xD3\x86\x02\xA3\x55\x63\x63\xD2\x98\x02\xD6\x44\xA3\x94\xA6\xA2\x02\xAD\x69\xA1\xAD\x35\x95\x62\xA2\x94\xC4\xA6\x8D\x16\x18\x48\x54\x6C\xD3\x47\xAA\x51\x90\xC6\x22\x56\x2B\x8D\x14\x19\x18\x64\x60\x06\xE6\x0E\x5C\xEF\xBD\xDF\xF3\x9C\xBD\xF7\x5A\xFD\x63\x7F\xE7\xCE\x9D\x99\x3B\x0F\x06\x06\xC7\x3A\x2B\x59\xF7\xBD\xCF\x3E'
b'\x7B\xAD\xBD\x5E\xBF\xB5\x2E\xA9\x2A\x4E\xD2\x89\x4D\xFC\xD3\x7E\x81\x93\x74\x64\x3A\xA9\xA4\x9F\x01\xA2\xB7\x7E\xF0\x22\xD4\xBD\x0A\x0B\x8F\xCE\x9F\xFA\xAE\x8B\xDE\xF9\x86\x33\x4E\x3F\xC3\x77\xFB\xDD\x78\xBC\xF6\x5B\x35\x35\xE3\xBE\xF4\xCF\x77\xED\xF9\xCE\x43\xF7\x7F\x6D\xD3\xA6\x0D\xC2\x05\xE7\xAB\x72\x80\xD7\x25\x02\x52\xCC\x4C\x86\xA0\x75\x82\x8C\x12\xC0\x04\x15\x05\x7B\x86\x29\x2D\x54\xF2\x42\x4A\x69\xDF\xBA\xA0\x48\x01\x20\x06\x20\x0A\xB2\x0C\x6E\x9B\xFD\xF6\x20\x55\xD0\x32\x57\x4F\x0C\xA4\x4A\x11\x6B\x01\x31\x01\x9A\x7F\xC6\x0E\xD0\xA4\xF0\x6B\x0A\xC8\x48\x30\x7A\xBA\x02\x39\x42\xB9\xCA\xA3\xFA\x49\x80\x5F\xEB\x50\x6E\x2C\xD1'
b'\x7F\xAC\x8F\xD4\x4F\xF0\x6B\x3C\xC8\x12\xA4\x12\xD8\x49\x9B\xDF\x01\x94\xF7\xB0\x84\xD8\x4F\x18\xCD\x56\xF9\x4C\x02\x18\x07\x18\xCF\x38\x5C\xD4\xB1\xC4\x40\x5A\x90\x77\x9A\x91\xB9\xE1\xEF\xBF\xF0\xC5\x33\x93\x24\x30\xD1\xF3\x57\xC7\x21\x48\x44\x50\x96\x25\x56\xB5\xA7\xBF\x26\x41\x2E\xB3\x53\xF6\x71\x8D\xD2\x9C\x63\x9F\xD0\x88\x40\x74\x32\x5E\x02\x80\xAD\xF6\x56\x17\x74\x1F\xE9\x6E\x33\x05\xA3\x2C\x0B\x1C\x97\x44\x82\x00\x48\xBE\x91\xAA\x0A\xCB\x16\x71\x90\x2E\x94\xD9\xEA\x3E\x2A\xCD\x59\x0A\xED\x41\x56\x5A\x47\x20\x3A\xE9\x91\x6D\xEF\xF1\xFE\x8D\xEC\x08\x64\x09\x46\xCD\x71\xD9\x44\x55\x01\x42\xF2\x6B\x8B\x6B\x4C\xC1\x8F\x57\xB3\xA3'
b'\xAB\x45\xF0\xCA\x38\x88\x1B\xEA\xF9\x70\x59\xEB\x94\xD6\x8D\x52\xED\xAF\x25\x25\x85\x06\x20\xFB\xA8\xE3\x67\xD9\x3F\x0B\x64\xB5\x96\x57\xB0\xE7\x83\x62\xC2\x0B\x49\x1A\x15\x7E\x4D\xF1\xB1\x72\x7D\xF1\x49\x76\x04\x2E\xF9\xAE\xC1\x13\x83\x5D\x9A\x74\x86\x19\xE7\xD8\x36\x43\x96\x19\x0C\x11\xA0\x0A\x84\x90\x20\xFA\xF3\xAE\x22\x80\xC9\x50\x75\x3C\x15\xD4\x10\x39\xFE\x81\x32\x00\x26\xD8\x69\x37\x20\xCB\xFD\xF1\xBE\x51\x93\x42\x65\x19\xAB\x22\x0E\x05\x22\x59\x61\x27\x32\x11\x13\xC8\xD0\x71\xBD\x49\x16\x58\x31\x1A\xBC\xA0\x44\x44\x88\xF3\xF5\x9F\xBB\x8E\x79\x88\x12\x9E\x1C\x3E\x3D\xBA\x4E\x46\x69\x33\x59\xC2\x81\xFB\x13\x00\x55\x82\x2A\x40'
b'\x6E\xFC\xFD\x58\x10\x64\x09\x60\x02\x24\x67\x4A\x59\x38\x04\x40\x81\x88\x6C\x7E\x94\x1F\x42\x8E\x73\xE2\x21\x00\xD9\x6C\xA6\x39\x13\x1C\x67\x5A\x3A\xFE\xFB\xE5\x7B\x1B\x02\x59\x5E\xCA\xC8\x48\x15\x9A\x04\xCD\x25\x5A\x52\x04\x01\xEC\x79\x9C\xA1\x29\x54\xD4\x43\x51\xB3\x65\xB0\xE7\x83\x15\xB6\x6C\x1D\xF1\x78\x0F\x03\x40\x08\x64\x34\x9F\xE3\x70\xD9\xDD\x73\x11\xF6\xB1\x12\x39\x82\xD4\x72\xEE\x60\xE7\xE0\x61\xB2\xB4\x37\x0D\x65\x33\xDB\x95\xAF\x1E\x59\x42\x58\x88\x88\x8B\x01\x63\x25\x82\x98\x90\xAA\xF4\xD6\x34\x48\x57\x80\x29\xA9\xA8\xE5\x82\xBF\xA5\x6D\xB9\x51\x53\x16\x9E\x29\x18\x60\x06\x59\x42\x9A\xAF\xAF\x88\x8B\xF5\xAF\x91\xA3\x08'
b'\x51\x26\xC7\x0B\xE5\x29\xAD\x2B\xE0\x59\x20\x8D\x34\x08\xA4\xFB\xDE\x81\x1D\xA3\xDE\x33\xFA\x70\x58\x08\x6F\x21\xC7\x11\x49\x99\x0B\xDE\x55\xAC\xF7\x57\x6A\x52\x35\xDE\x20\xD6\x11\x6C\x09\xAA\x38\x7D\xB8\x67\xF4\xEE\x38\x88\xE7\xD7\x0B\xD5\xCB\x06\x4F\x0C\x5A\x1A\x74\x81\x1D\x3F\x28\x22\x5F\x2F\xD6\x15\xDB\xD8\xF3\x33\x8D\x72\x34\xEA\x52\x68\x4D\xA3\xF4\xEA\x30\x57\x7D\x94\x2C\x89\x0A\x8C\x78\x7A\x50\x5B\xE6\xBA\xC3\xA6\xE0\x00\xDC\xF3\x57\xC3\x11\x48\xD1\xDC\x3A\xAF\x95\x6E\x66\x77\x68\xDF\xA0\x4C\x40\x9D\x20\xBD\x1A\xE4\xB3\x05\x30\x13\xA4\x4A\xAF\x89\xBD\xF4\x76\x62\xCA\x37\x3B\xF0\x7A\x22\xBD\x51\x82\x82\x18\xB0\x13\x6D\xB0\x23'
b'\xC0\x32\xA8\x17\x63\xEC\x0E\xDF\xD6\xC4\x5A\x89\x0A\xBF\xDA\x7F\xC1\xAD\xF2\x77\x4B\x95\x96\x82\x1E\x35\x52\x64\x40\x45\x5D\x3D\x57\x7F\x5A\x46\x09\x64\x19\x69\x98\xD0\xDA\xDC\xFA\x8A\x9B\x29\x54\xA3\xC2\x4D\x3B\xC4\x5E\x44\xE8\x86\x4F\xA9\xE8\xEF\x4B\x95\x68\xB9\x85\x92\xA1\x8D\xA9\x4E\x2F\xAF\x17\xEA\xAD\xF5\xB3\xF5\x0D\x7E\x8D\xFF\xA3\x89\x33\x27\x6E\x84\x28\x52\x4C\xD0\x04\x90\x10\xA4\x4E\x67\xC6\x6E\xFD\x5B\xEC\x18\x9A\x14\x28\xED\xEB\x88\xE8\x3A\x95\x43\x6B\x89\x25\x6A\x2B\x09\x90\x14\x48\x72\x00\x8F\x7F\xA6\xF1\x85\x09\x5A\x4B\xFE\xFB\x70\xA4\x00\x96\x5C\x42\x66\xE4\xCF\xF3\x64\x19\xCB\x78\x37\x19\x06\x3B\x06\x11\x41\xAA\x04'
b'\x15\x40\x46\x09\x76\xC2\xFE\x9D\x99\xB0\xB3\x20\x02\xB9\x2C\xC8\xD4\x8F\x97\xBA\x8E\x81\x6B\x1B\xB8\x8E\xC9\x05\x64\x52\xA8\x00\x6C\x18\xB1\x1B\x2F\x94\x4A\xC0\xA5\x01\x59\x02\x97\x0C\xBF\xCA\xFD\x31\x25\x80\x2D\xA1\x9E\xAB\xCD\x70\xD7\xF0\x5B\x71\x10\xAF\x82\x82\xB8\x34\xB9\x08\x27\xE4\x0F\x0A\x90\x61\x98\x96\x81\x26\x9D\x1A\x3E\x39\xFC\x74\xEF\xD1\xDE\x67\xC1\xD9\x4A\xC9\xA0\x71\x77\xFD\xFD\xCF\x41\x3B\xC9\x8C\xDD\xF9\x21\xD8\x62\x5D\xF9\xFE\xB9\xD9\xF8\x76\x36\x44\xA0\x03\xE2\x93\x2A\xC4\x70\x6C\x59\x3D\xA3\x33\xAA\x5F\xCE\x9E\x5F\x8C\x1C\xE3\x39\x93\x4A\xB6\x26\xE3\x09\x12\x15\x5C\x58\x71\x93\xEE\x8E\xD1\x9E\xEA\x03\xC6\x10\xC8'
b'\x11\x62\x37\x5E\x5A\xCF\xD5\x57\x42\x73\xBD\xC6\x9E\xE1\xA6\x1D\x54\x01\x53\x18\x0C\x76\x0D\xB7\x36\xB7\x59\x82\xC0\xB6\xED\x8F\x6C\xDB\xFE\x20\x07\x2F\xC2\xE2\x83\x8B\xFF\x18\xFB\xF1\x17\xCD\x18\xB9\x90\xA0\x60\x43\xCA\x9E\xEE\x65\x6F\x76\x4B\x94\xD3\xA4\x4A\x6F\x94\x7A\x8C\x86\x18\x83\xC1\xCE\xC1\xFB\xD9\xD0\xFF\x4E\xBC\x6C\xE2\x33\xDA\xC7\x31\x67\xD0\xF6\xBC\x33\xED\xCD\xBF\x33\xB9\xFB\x66\x05\x23\x1E\x70\xC9\x8D\x02\xA5\x55\x7C\x76\xF2\x54\xDC\xB3\xB3\xB8\x6E\xED\x6C\xF7\x13\x70\x27\x5E\x71\xA9\xC8\xD6\x4E\x86\xC0\x60\xB0\x25\xB8\x19\x77\x7B\xB5\xB7\xFA\x00\x00\xB0\x65\xA4\x41\x7A\x69\x35\x17\x5E\x6D\x27\xEC\xF7\xA4\x16\x70\x93'
b'\x0C\x00\x48\xBD\x88\xD4\x8D\xBF\xC9\xE3\xB3\x69\x54\x98\x09\xBB\x8D\x4A\x03\x32\xC0\x70\xF7\x68\x6B\x1C\xC4\x5F\x37\xAD\x5C\x47\x4A\x2D\xB0\x6D\xFB\xD5\xD6\xE6\xD6\x95\x71\xB1\xDE\xC1\xDE\x00\x8E\xC0\x84\xF3\x06\x4F\x8D\xFE\x36\x0D\xD3\xB9\xEC\xB3\x55\x0D\xF7\x8C\x6E\xB2\x53\xEE\x56\xF6\xFC\x2C\x97\xC7\x26\x3B\xFB\xBE\xF0\xF4\x9F\xBE\x76\xAA\xF7\xAE\xB1\xED\x1E\x98\xE9\x11\x24\xF8\x4D\x96\xBE\x7C\xF5\xE9\xA7\x5D\xF9\x5F\x5D\xFF\x2B\x13\xC3\xFA\x7C\x3E\x01\x15\x25\x51\x00\xD1\x6C\xFD\x95\xC0\xB6\xED\x7F\xD8\xB6\x79\x22\x8E\xE4\x54\xB6\x04\x15\x81\x46\xB9\xD4\x76\xEC\xF7\xC4\x27\x68\x10\xC4\x6E\x04\x7B\x46\xEC\x85\x37\xA5\x61\xDC\x48'
b'\x2E\xC7\x30\xB6\x84\x62\xB5\xBF\x8D\x0C\x21\x8D\x12\x46\xB3\xA3\x3F\x23\xCE\x69\xB6\xD4\x02\x3B\x61\xEF\xED\x9C\xD6\xBE\x04\x4C\x48\x41\xB2\xCB\x64\x03\x9E\xF2\xDF\x2F\xB7\xF0\x9B\xFB\x8F\xF6\x16\x34\xAA\x6D\xD6\x0F\x77\x0F\xFF\xA0\xDC\x54\x7E\x94\x8B\x15\x32\xBF\xA3\x20\xFB\xDA\xC1\xFC\xC7\x7B\xEC\x01\x1C\xBC\x5E\x01\x08\x0C\x4E\x1B\xCC\x7D\xF8\xE2\x81\xBF\xF2\xDF\x68\xED\xBD\x93\x14\xCE\x7F\xDE\x12\x7D\x81\x89\x98\xA0\x51\x21\x63\x6B\x82\x00\x5C\x30\xEC\x84\xBB\x23\xF4\x86\x57\xC1\x1A\x80\x09\x69\x10\x2F\x25\xD5\xEB\x8C\x21\xA8\x32\xB8\x43\x30\x85\x41\x98\x0F\x5B\x25\x29\x8C\xCB\xAE\xCE\x4D\xB9\x07\x8A\xD5\xFE\xA1\xBA\x1B\x51\xCF'
b'\xD7\xE7\x4A\x25\x2F\x67\xC7\x39\x9D\x07\xA1\xBD\xA5\xFD\x5E\xBF\xCA\xA3\x5E\x0C\x20\x22\x70\x69\xC0\x8E\xC1\x9E\xD0\x59\xD7\x1E\x48\x3F\xFE\xF6\x60\xE7\xE0\x12\x53\x9A\x01\x81\xDA\x6E\xDA\x3D\x5A\xAC\x2B\x20\xB5\x1C\x93\xCB\xB3\x3D\xB6\x7D\x00\x1D\x60\xE5\xF5\x04\x00\x6C\xE3\x30\x81\x29\x6A\xFB\x44\x2C\xFF\x89\x72\x9C\x11\x05\x0C\x71\x86\x94\x40\x30\x53\x6E\x1B\xED\x1D\x5D\x05\x2C\xB9\xBC\x73\x42\x2F\x9D\x65\x3B\xF6\x11\x85\xE4\xD2\x40\x14\x71\x10\x2F\x6D\xD2\x7D\x4D\x0A\x37\xE3\xB6\xA9\xC9\xB5\x5A\x1A\xA6\x0B\x34\x65\xE5\x4B\x10\xB8\x49\x77\x9F\x29\xF9\x91\xB0\x18\xA0\x41\xE1\xD7\x16\x00\x13\x64\x94\x10\x16\x6A\xB0\x67\xD8\xB6\xB9'
b'\xD3\x4D\xBB\x3B\xD9\x31\xA4\xA5\x68\x6D\x6C\xC1\x94\x06\x52\x3D\x47\x25\x69\x86\xD4\x18\x40\x3A\x9A\x3F\x17\x22\x10\x9D\x88\x2A\x02\x40\xE3\xA2\xB2\x16\x80\x32\x62\x21\x75\x82\xED\x98\xFB\x4D\x69\x1E\x6D\x50\x76\x0D\x82\x38\x08\x17\x53\xCB\x00\x86\x40\x4C\x88\xBD\xF8\xCA\x38\x48\x67\xB1\x1D\xBB\xBA\x82\x01\xA6\xDB\xC3\x42\x44\xB9\xD6\xC3\x78\x3E\x5B\xD2\x38\x0A\xE4\xDF\x7F\x5B\x92\x22\xD5\x92\xE3\x50\x69\xC0\xE3\x2C\x13\x44\x18\xED\x19\x01\x86\xD0\x3E\xAD\x8D\x62\x53\x81\xD6\xE6\x12\x71\x10\x51\x3F\x53\x8F\xDF\xF5\xE8\x8E\xA4\x9A\x63\xA6\x9D\xB0\x2F\x4E\x31\xFB\x62\x50\x83\x0A\xB0\x65\xA8\xE4\x7B\x67\x4A\x03\x37\xED\x6E\x1B\xEE\x8A'
b'\xD7\x18\x0B\x80\x08\xD2\x4F\x6F\x63\x92\xBF\x82\x41\x76\x81\xBD\xB8\x55\xA3\x00\xD6\x40\xA2\xC2\x4D\xD8\xEF\xD8\x16\xEF\x90\x41\x40\x9D\x12\xA4\x4A\xAB\xD8\xE4\x18\x4C\x96\x20\xB5\x3C\x33\xDA\x3D\xCA\x37\xBC\x65\xC0\x8E\xA0\x41\xCF\x49\x83\xF4\x87\xFB\x65\xC7\x63\x20\x44\x55\x0D\x31\x3D\x66\x3B\xF6\xDA\x72\x63\x99\x7B\x55\x47\x3A\x8B\x2A\x90\x00\x3B\x6D\x61\x3B\xFF\x8F\x94\x04\x64\x4B\x61\x4F\xB9\x5B\xA7\x00\x3B\x82\x5F\xE3\x6F\x1F\xED\x19\x5D\x03\xCD\x50\x4E\xEC\xC7\x37\x0F\x77\x8D\x36\xB9\x69\xF7\x94\x69\x19\x84\x7E\xDA\xBA\x24\x38\x51\x98\x8E\xB9\x8D\x0D\x21\x85\x71\xF6\x77\x28\xD0\x4C\x81\xD4\x0F\x48\x0C\x48\xD0\xB3\x53\x37\xBD'
b'\x67\x3F\x2B\x19\xBB\x35\x4D\x0A\x2E\x78\x9E\x88\xAE\x3D\x2A\x57\x97\x13\x01\xB8\x69\x07\xD3\x31\x79\xFD\xB1\x8B\xE4\x04\x22\xC1\x52\xC2\x10\x7B\xB9\xA9\x4C\x0C\x20\x2A\xFC\xB4\xFB\xA1\x9B\xB4\x3F\x94\x90\x5D\x5E\xAA\x13\x52\x90\x4B\xC8\x32\xAA\xD9\xE1\xD9\x69\x18\x5F\xC5\xE3\xAC\x8E\x1C\x81\x5B\xF6\xCE\x14\x09\x6E\x95\x47\xB1\xA9\x05\x6E\x99\x45\x89\x59\x53\x1A\x15\xEC\x79\x75\x6B\x4B\x1B\xC5\xFA\x22\x63\x82\xE3\xC2\x9B\xDD\x98\x2D\x83\x98\xC0\x9E\xF7\xB1\xE3\x1F\xD3\x21\x60\xB0\xFD\x48\x73\xCD\xE7\xA6\xF6\x29\x08\x9A\x61\xA1\xA3\xF1\x92\x4D\xEB\xED\xC4\x8B\x49\x92\xDD\x83\x9F\xF1\x30\xA5\x41\xEA\x25\x48\x25\x68\xF0\x53\xDB\x32\x30'
b'\x6D\x7B\x6B\x58\x88\xD7\x37\x4B\x74\x94\x2E\x61\x47\x37\xC7\x5E\xBC\x48\x83\x82\x0A\x82\xD4\x02\x37\xED\xEF\xF6\xD3\x76\x97\xD4\x82\xD1\xB3\x15\x68\xBE\x46\x1A\xA6\xC7\x96\x97\x1C\x52\xCB\x6B\x60\x00\x6E\x59\xF8\x09\x41\xEC\x45\x28\xE9\x7F\x83\xF0\x71\x85\x26\x15\x04\xDB\x32\xAB\x24\xE8\x35\x0D\x28\xAB\xA2\x41\xC2\x11\x70\xEC\x46\x41\xD3\x0E\xA6\x6D\xF6\x43\x79\x6C\x5B\x65\x52\xB0\xE2\x98\x41\x23\x03\x00\x62\x49\x04\x29\xEA\xB4\xA6\x23\xD8\xEC\x18\xE9\x7D\x31\xA8\x69\x6B\xF8\x19\x9F\xE1\x98\xA8\x19\x87\x0B\x92\x67\x21\x00\x84\x7E\x02\xB7\xEC\x1D\xE4\xE8\xFA\xEC\x02\x19\xB1\x17\xCF\x4F\xC3\x08\x89\xFA\xA6\xC6\x97\xE4\x1B\x6C\xB7\xF1'
b'\x18\x2E\x32\x85\xC9\xD6\x39\xE3\xBF\x1D\xE6\xC3\x92\xA5\xC5\x6E\x7C\x8B\x0C\xD2\x26\x37\xED\x9E\x12\x43\x30\x8E\xA1\x51\x76\xC4\x24\x37\x80\x81\x72\x53\x0B\xA6\x63\x57\xF7\x1E\xE9\x5D\x43\x86\xA0\x41\x61\x3B\x76\x6F\xB9\xA1\xCC\x0A\x5B\x01\x49\x55\xD5\x26\xAB\x84\x6D\x5B\x34\x96\xDB\x10\xFF\x24\xD0\xE7\x43\xAD\xD5\x62\xA0\x41\x2F\x50\x6F\x39\x77\x03\xF5\xA9\x96\x1A\x43\xFB\xEF\xDB\xA7\x27\x64\x66\x55\xFF\x5F\x08\xBD\x79\xB6\xC3\x8A\xED\xB0\x77\x10\xBB\xD1\x3C\x61\x51\xA4\x0E\x38\xEE\x83\x48\x0A\x85\x00\x7E\x7A\x99\x82\x1A\xA2\x5C\x90\xFA\x29\x97\x07\x51\x3C\x3F\xC2\x85\xF9\xBE\xA6\x3C\x64\xA2\x49\xDB\xD5\x6C\xF5\x09\x0D\x72\x6E\xD3'
b'\x6E\x30\x85\x51\x33\x61\xBF\x24\x75\x16\x90\x2D\x0D\x6C\xCB\xC0\x4F\xD8\x7B\xC9\xD2\xAC\x26\x6D\xD6\xF2\x70\xCF\xF0\x66\x10\x10\x07\x09\xEA\x19\x76\x4D\x01\xB7\xAA\x80\x5F\x5D\xA2\xF3\xD2\x09\x84\xF9\x70\xB3\x34\x19\x65\x52\xD8\xB6\xBD\xCF\x4D\xBB\xE6\xBD\x0F\x38\x47\x8E\x7B\x6E\x26\x5B\xD0\x81\x0A\x02\x00\xFB\xC9\x2D\x67\x5F\xB6\x28\x7C\xF9\x20\x00\x86\xF6\x7F\x44\x52\xA0\xC5\x6A\xB7\xB8\x10\x1F\xAA\x4B\x6C\x3E\x75\xD3\x36\xBF\x79\x7E\x1B\xFB\x91\x85\xF2\x41\x13\x45\x64\x81\x34\x2C\xD1\xDF\xF1\xF4\x9F\x84\x6E\x7D\x0D\xBB\xE3\x94\x97\x64\x10\xB6\x5F\xAE\x2F\xC1\x05\x67\x48\xC8\x2F\xB3\x5E\x55\x80\x19\xB6\x64\xE8\xA4\x45\x0A\x0A\x3B'
b'\x69\x6F\xAD\x7A\xF1\x3C\xB2\x39\x7E\x55\x7B\xAB\xEB\x88\x73\x1A\x2E\xB5\xC0\x4E\xDB\x6F\xD8\x92\xF7\x48\xC8\x22\x50\x11\x68\x45\x30\x1D\xA3\xC5\xBA\xE2\xFA\xFE\x8F\xFB\x7F\x6D\x9D\x05\x17\x8C\x30\x1F\x2F\xEE\x6E\xEF\xDE\x62\x0A\x73\x35\x1B\x9A\xB3\xD3\x36\xB7\xB4\x7A\xB1\xDD\xDB\xDE\xBD\xB6\x7A\xA6\x7A\x87\xF1\x26\x23\xDF\x8E\x61\x27\xED\x17\xA4\xCA\x75\xD9\x41\xE7\x00\x0D\x8B\x75\x05\xB8\x5C\xE1\x1C\x63\xB2\x0F\xFC\xF2\x4B\xB7\x96\xEB\xEC\x6F\x60\x90\x58\x0F\xD0\x33\x01\x58\x28\x0D\x3F\x3E\xEC\x7E\x37\xFC\x2B\x7D\xD6\x0F\xAF\x78\x45\x2A\x37\x7C\x30\xE9\xC8\xAF\x00\x21\x01\xD0\x82\xA6\x3A\x0F\x4E\xBF\xEA\x53\xD7\x2E\xFE\xCF\x3D\x1B'
b'\xEB\x79\xBA\x8C\xFD\x71\x98\x9B\xC8\xA3\x5A\xBF\x54\x3D\x5B\x7F\x4E\x93\x16\x38\x30\x56\x26\x35\xE4\xF8\x19\x53\xB4\x3F\x62\x1C\x05\x82\x02\xD3\xEE\x8E\xFA\x99\xEA\x2F\x9B\xBE\x4E\x03\xF3\x00\xD9\xDD\xB8\x09\x7B\x2B\x33\x41\x35\x1F\xCB\x94\x76\xA9\xB1\xD8\xDA\xD2\xBA\xA9\x9E\xAB\x7F\x37\x0D\xD2\x2B\xB9\x64\xB0\x23\xD4\xCF\xD6\x97\xB3\xE7\x77\xD8\xB6\xF9\x7A\xE8\xC5\x27\x35\xC8\xC6\x38\x48\x17\x48\x95\x36\xE6\x9A\x29\x23\xF2\xAD\x53\x5A\x37\x15\x6B\x8A\xC7\x52\x75\x70\x39\x4A\x0C\xA8\xEA\x39\xD5\x5C\x7D\x8B\x26\x35\x38\xD8\xFD\x18\x00\x3D\xBB\xE5\x4C\xF7\x25\x72\x04\xAC\x3E\x84\x7B\x62\x82\x9B\x8C\xEF\xDD\xBD\xFD\x75\x9F\x7B\xEA\xE1'
b'\xF3\x3E\x64\x66\x8A\xCB\x0F\xD7\xD2\xD6\x0A\x30\x93\x6F\xFE\x8A\x69\x7D\xF3\x6F\x30\x9F\x2E\xCB\xFB\xBC\xB0\x34\xF6\xF5\xA7\x57\xB3\xA3\xDF\x5B\xF1\x1D\xC6\x28\x77\xB9\xA9\xF8\x18\x5B\x0E\x12\x15\xA6\x65\x76\x9A\xB6\xBD\x2F\x2E\x86\xD7\xEF\xD7\x3D\xCD\x71\x4A\xCC\x84\xFD\x72\x8A\x19\xA9\x50\x55\x60\x98\xC0\x9E\xB3\x2B\x6C\x19\xB4\x4E\x6B\x5F\xD8\xDF\xDE\xBD\x5F\x46\xF2\x12\x2E\xC6\x1D\x58\xC5\x4C\xBD\x10\xDE\x8E\x14\x00\x93\x5D\xEC\x52\x0F\x2B\x83\xB0\x77\x97\xEB\x8B\x0F\x2D\xB5\x67\x0E\x74\x75\x86\xA0\x51\x37\x54\xB3\xA3\xCB\x0F\x77\x5E\x2B\xB5\x54\x18\xA1\x38\x64\xDE\xA6\x80\x6D\x89\x72\xD1\x26\x28\xD5\x44\x00\x51\x75\x18\x09\x16'
b'\xD0\x9A\x3B\x12\x52\xEB\x98\x72\x41\x1A\x27\x04\x51\x01\x6E\x8A\x0D\x40\x93\x4E\x69\x14\x80\xF7\x5D\xA6\x43\x26\x28\x3A\xEE\x5D\x61\x99\x70\x98\x60\x3B\xF6\x1F\xEA\x67\xAB\xD7\x2F\x5F\x27\x95\xC0\xAF\x2D\xBE\x61\x27\xDC\x9C\x04\xC9\x48\x84\xEE\xF3\xFB\xC4\x04\xAD\x15\x80\xEE\x6D\x6D\x69\x9F\x1B\x16\xE3\x2D\x61\xBE\x7E\x07\x30\x6E\xB7\x73\x6E\xEB\xAB\x64\xEC\xB0\xD1\x84\x9B\x76\xB7\xD8\x8E\x7D\x9F\xA6\xAC\xB0\x06\xA5\x57\xD1\xB6\x44\x01\x2D\xBB\xE9\x47\x4A\xB4\x2C\x14\x15\x08\xC5\xE1\x85\x86\xA8\x29\x00\x14\x23\xE0\x0F\xFB\xC0\xBC\x26\x45\x10\x1D\x5B\x40\x92\xEC\xC7\xB9\x63\xC1\xCB\xDA\xE7\x30\xF4\x90\x82\xEE\x25\x3E\xDC\x0D\xC9\xA4'
b'\xA2\x96\x1D\xCF\x92\x21\x69\xE6\x16\x14\x80\x5B\xED\xB7\xF9\x5E\xB8\x98\x00\x0B\xA2\x04\x80\x92\x4B\xAE\xDC\x50\xFC\x85\x6D\x1B\x48\xB5\x4F\x58\xAA\xBA\x6F\x06\x82\x00\xCD\x7D\xA2\xF9\x72\x43\xF1\x4E\xE3\xF9\x7C\x4D\xF2\x9E\x38\x48\x6F\x00\xB0\x45\x93\x96\xEC\x79\x48\x86\x1E\x67\x4B\xF7\xD8\x09\xFB\x45\xB2\xF4\x5D\xAD\xF3\x7C\x04\x30\xAE\xE3\x18\x30\xDE\x3C\xE9\x26\xDD\x3D\x6C\x39\xE2\xC8\x48\x9E\x01\xD0\x3D\xE1\x10\x07\x4D\x02\x3B\x61\xC1\x66\xF9\x4D\x03\x28\xE8\x5D\x54\x98\xBB\x8E\x66\x56\xB2\x81\x87\x9A\x98\xC2\xAA\x10\x02\x4C\xC9\xB3\x7E\xB5\xFF\xD5\x3C\xB0\x92\x9F\x6F\xA2\x41\xD3\xC8\x5B\x1E\x11\x08\x74\x50\xC6\xA8\x29\xA7\xCA'
b'\x76\xD2\xDE\x43\x4C\xF7\xF8\x35\x0A\x10\x4D\x8D\xF6\x8E\xDA\xAD\xF5\x65\xDF\xB6\x6C\xB7\x9A\xAB\xC1\x05\x23\x0D\x23\xB4\x89\xDA\x36\x3F\x8F\x0C\xC1\x94\xE6\x3F\xFD\x6A\x7F\xC1\x11\x3B\xD4\xCB\xC8\xE2\xB8\xE7\xCA\x47\xA4\xFD\xF7\xD7\xE6\xD6\xE5\x96\x78\x33\x97\xAD\x49\x97\xEA\xA0\x23\x91\x8A\x42\x31\x4E\x83\x96\x19\xC4\xD2\x33\x9A\xA9\x22\x64\x14\xA1\xA9\xEC\x8F\x16\xA1\xD6\x98\x9F\xCF\x05\x81\x98\x16\x41\x58\x6C\x5C\x96\x04\x39\x78\x44\x40\xF7\xF1\x92\x2B\x7F\x0E\x68\x38\x93\xA5\xE2\xF8\x0F\x75\x1D\x82\xF2\x8B\xDB\x26\x15\x6E\x18\x0A\x70\xC9\xE3\xAF\x4F\xC4\x86\xFD\x98\xC6\x42\x5F\x12\xFE\x61\x86\x49\x9E\x0F\xB1\x54\xF2\x28\x97\xC7'
b'\x77\x82\x75\x45\x1A\xCF\xC7\x01\xFA\x88\x8C\x12\xA4\x5A\xC6\xA3\x84\x06\x6B\x3B\x49\x00\x77\x1F\xE8\x7D\xC4\xB4\x4D\x4E\x1D\x91\x0B\xF9\x7D\xD6\x99\x61\x17\xB2\xEA\xC8\x94\x50\x69\x4F\x35\x98\xD8\xCA\x3C\x0E\x92\x68\x17\x9A\x52\xAB\x71\x3B\xAA\x2B\x70\xAD\x30\x9E\x47\xE5\x86\xF2\xF3\xE4\x0C\xB8\x58\xC6\xA5\x81\x2D\x2D\xD8\x9B\x13\xDA\x90\x5E\x2C\xE2\x6A\x77\xF5\xD5\xFE\xC3\x83\x0F\xC5\x18\xE7\x03\x05\x44\x8D\x88\x1A\xC6\x1C\x11\x6D\x44\x35\x4F\x4F\xA7\xB9\x1D\xC2\x7E\xFB\x0F\xEB\x00\xD4\x11\x08\x2B\x70\x1D\x2C\x62\x8C\x80\xD9\xFE\x84\xE9\xB4\x7E\x1C\x62\x42\x5D\xD5\xA8\x43\xD8\xC7\x75\x40\x35\xAA\x21\x24\x0F\x74\x4E\xED\xBC\xB1\x98'
b'\xF2\xB3\x06\x80\xB5\x7C\x30\x17\x79\x5C\xEB\xE7\x5D\x51\xD6\x38\x46\x7F\x6F\xFF\xA6\xB2\x5F\xDE\x7D\xD6\xE6\x33\xDE\xE8\x56\x5B\x2F\x41\x72\x94\x62\x90\x83\xA3\x9D\x3B\x7F\xF4\xED\xFE\xC2\x13\x68\x75\xBE\x79\xCB\x4B\x5E\xB2\xB6\xC7\x3C\x53\x8A\x84\x83\x4A\xE8\xC2\x59\xB3\x77\xF1\xE1\xC7\x9F\x9C\xBB\x7F\x77\x31\x33\xB9\xFB\x17\xCE\x3B\xE5\xDD\x45\x2A\x36\x26\x4A\x19\x42\x52\x80\x99\x0D\x4D\x99\xC5\x1D\x4F\xEC\xF8\x27\xB5\xBA\x17\x4D\x30\x3F\x90\x1A\xC4\xF7\xA4\xCB\x03\x9D\xFC\xC7\xE6\x13\x9F\x7E\xDA\xE9\xF7\x49\x3A\x0A\x3A\xA9\xA4\x9F\x01\xFA\x3F\xC6\x7E\xAA\x10\x6B\xC7\x4E\x59\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82'
)
test_img_lvgl_logo_png = lv.image_dsc_t(
dict(
header=dict(cf=lv.COLOR_FORMAT.RAW_ALPHA, w=105, h=40),
data_size=6878,
data=test_img_lvgl_logo_png_data
)
)
image_draw_dsc = lv.draw_image_dsc_t()
image_draw_dsc.init()
image_draw_dsc.src = test_img_lvgl_logo_png
coords = lv.area_t(
dict(
x1=10,
y1=10,
x2=10 + test_img_lvgl_logo_png.header.w - 1,
y2=10 + test_img_lvgl_logo_png.header.h - 1
)
)
lv.draw_image(layer, image_draw_dsc, coords)
# Reuse the draw descriptor
coords.move(40, 40)
image_draw_dsc.opa = lv.OPA._50
lv.draw_image(layer, image_draw_dsc, coords)
line_draw_dsc = lv.draw_line_dsc_t()
line_draw_dsc.init()
line_draw_dsc.color = lv.color_hex3(0xCA8)
line_draw_dsc.width = 8
line_draw_dsc.round_end = 1
line_draw_dsc.round_start = 1
line_draw_dsc.p1.x = 150
line_draw_dsc.p1.y = 30
line_draw_dsc.p2.x = 350
line_draw_dsc.p2.y = 55
lv.draw_line(layer, line_draw_dsc)
canvas.finish_layer(layer)
c = lv.color_hex(0xff0000)
for i in range(50):
canvas.set_px(100 + i * 2, 10, c, lv.OPA.COVER)
@test_func_wrapper
def main():
lv.tick_inc(300)
lv.refr_now(None)
test_event()
create_ui()
main()
# end

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,594 @@
#!/usr/bin/env python3
import argparse
import sys
from pathlib import Path
import os
import shutil
import subprocess
import os
from typing import Optional
perf_test_options = {
"OPTIONS_TEST_PERF_32B": {
"description": "Perf test config ARM (so3) Emulated - 32 bit",
"image_name": "ghcr.io/smartobjectoriented/so3-lvperf32b:main",
"config": "lv_test_perf_conf.h",
},
"OPTIONS_TEST_PERF_64B": {
"description": "Perf test config ARM (so3) Emulated - 64 bit",
"image_name": "ghcr.io/smartobjectoriented/so3-lvperf64b:main",
"config": "lv_test_perf_conf.h",
},
}
lvgl_test_dir = os.path.dirname(os.path.realpath(__file__))
def main() -> bool:
epilog = """This program runs LVGL perfomance tests
In order to provide timing consitency between host computers,
these runs are run in an ARM emulated environnement inside QEMU.
For the runtime environnement, SO3 is used which is a lightweight, ARM-based
operating system.
Right now, this script requires a host linux computer as we depend on
`losetup` which is used to set up and control loop devices.
"""
parser = argparse.ArgumentParser(
description="Run LVGL performance tests.", epilog=epilog
)
parser.add_argument(
"--build-options",
nargs=1,
choices=perf_test_options.keys(),
help="""the perf test option name to run. When
omitted all build configurations are used.
""",
)
parser.add_argument("--test-suite", default=None, help="Select test suite to run")
parser.add_argument(
"--pull",
action="store_true",
default=False,
help="Pull latest images from registry before running tests",
)
parser.add_argument(
"--clean",
action="store_true",
default=False,
help="Clean existing build artifacts before operation",
)
parser.add_argument(
"--auto-clean",
action="store_true",
default=False,
help="Automatically clean build directories",
)
parser.add_argument(
"--debug",
action="store_true",
default=False,
help="Launch QEMU in debug mode allowing you to connect to gdb",
)
parser.add_argument(
"--debug-port",
default=5555,
help="Port used to connect to QEMU in debug mode",
)
parser.add_argument(
"actions",
nargs="*",
choices=["build", "generate", "test"],
help="build | generate: generates run files dependencies, test: run performance tests.",
)
args = parser.parse_args()
options = []
if args.build_options:
options = args.build_options
else:
options = perf_test_options.keys()
# Since test suites are run inside a virtual space, debugging them is tricky
# because every test suite will be loaded into memory at the same virtual address
# this means that when you try to set a breakpoint (e.g: in `main`), gdb will actually
# hit that breakpoint for EACH test_suite that runs, which can be very confusing
# Instead, only allow debugging if a test_suite is selected as we won't encounter that problem
if args.debug and not args.test_suite:
print("Please provide the test suite you want to debug")
exit(1)
is_error = False
for option_name in options:
if any(action in args.actions for action in ("generate", "build", "test")):
if args.clean:
clean(option_name)
generate_files(option_name, args.test_suite)
if "test" in args.actions:
ret = run_tests(
option_name,
"lv_test_perf_conf.h",
args.pull,
args.debug,
args.debug_port,
args.test_suite,
)
is_error = is_error or not ret
if args.auto_clean:
clean(option_name)
return is_error
def write_lines_to_file(path: str, lines: list[str]) -> None:
"""
Writes a list of strings to a file, each on a separate line
"""
with open(path, "w") as f:
f.write("\n".join(lines))
def lvgl_test_src(name: str) -> str:
"""
Gets the absolute path of a file from the lvgl `tests/src` folder
"""
return os.path.join(lvgl_test_dir, "src", name)
LVGL_TEST_FILES = [lvgl_test_src("lv_test_init.c"), lvgl_test_src("lv_test_init.h")]
def options_abbrev(options_name: str) -> str:
"""Return an abbreviated version of the option name."""
prefix = "OPTIONS_"
assert options_name.startswith(prefix)
return options_name[len(prefix) :].lower()
def get_base_build_dir(options_name: str) -> str:
"""Given the build options name, return the build directory name.
Does not return the full path to the directory - just the base name."""
return "build_%s" % options_abbrev(options_name)
def create_dir(build_dir: str) -> bool:
created_build_dir = False
if os.path.exists(build_dir):
if not os.path.isdir(build_dir):
raise ValueError(f"{build_dir} exists but is not a directory")
else:
os.mkdir(build_dir)
created_build_dir = True
return created_build_dir
def find_c_files(directory: str) -> list[str]:
c_files = []
for root, _, files in os.walk(directory):
for file in files:
if file.endswith(".c"):
c_files.append(os.path.join(root, file))
return c_files
def get_container_name(options_name: str) -> str:
"""
Returns the docker container name based on the options name
"""
return f"lv_perf_test_{options_name}"
def get_docker_volumes(options_name: str) -> list[str]:
"""
Returns all docker volume names that should be created in order to run the images
This function is useful so we can easily loop through the necessary docker volumes in order
to create or delete them eg:
```python
for v in get_docker_volumes(options_name):
subprocess.check_call(["docker", "volume", "create", v])
subprocess.check_call(["docker", "volume", "remove", v])
```
"""
return [get_build_cache_volume(options_name), get_disk_cache_volume(options_name)]
def get_build_cache_volume(options_name: str) -> str:
"""
Returns the docker volume name for storing cmake generated files
We use a docker volume here instead of a plain directory because the user that launches the script
will not own the generated files as they're generated inside the docker container
Using a docker volume allows us to delete it without having to run delete the volume inside this script
without admin permissions by removing the docker volume:
```python
subprocess.check_call(["docker", "volume", "remove", get_build_cache_volume(options_name)])
```
"""
return f"{get_container_name(options_name)}_build_cache"
def get_disk_cache_volume(options_name: str) -> str:
"""
Returns the docker volume name for storing the virtual disks generated inside the docker volume
We use a docker volume here instead of a directory because the user that launches the script
will not own the generated files as they're generated inside the docker container
Using a docker volume allows us to delete it without having to run delete the volume inside this script
without admin permissions by removing the docker volume:
```python
subprocess.check_call(["docker", "volume", "remove", get_disk_cache_volume(options_name)])
```
"""
return f"{get_container_name(options_name)}_disk_cache"
def get_build_dir(options_name: str) -> str:
"""Given the build options name, return the build directory name.
Returns absolute path to the build directory."""
return os.path.join(lvgl_test_dir, get_base_build_dir(options_name))
def generate_so3_init_commands(runners: list[tuple[str, str]], path: str) -> None:
"""
Generates the `commands.ini` file that will be mounted in `usr/out/commands.ini` replacing
the default `commands.ini` used by so3.
A `commands.ini` file declares some specific commands the `init` program should run
sequentially. We use this feature to run every perf test.
An example that runs tests A and B before exiting looks like this :
```
run test_A.elf
run test_B.elf
exit
```
"""
output = []
for runner, _ in runners:
name_without_extension = Path(runner).stem
label = f"Running {name_without_extension}"
delimiter = "=" * len(label)
output.append(f"echo {delimiter}")
output.append(f"echo {label}")
output.append(f"echo {delimiter}")
output.append(f"run {name_without_extension}.elf")
output.append("exit")
write_lines_to_file(path, output)
def generate_perf_test_cmakelists(runners: list[tuple[str, str]], path: str) -> None:
"""
Generates the CMakeLists.txt that will be mounted in `usr/src/test_src/CMakeLists.txt`
This file simply declares every runner as a different executable and links the necessary
libraries
"""
output = []
files_to_include = " ".join(
os.path.basename(file) for file in LVGL_TEST_FILES if file.endswith(".c")
)
for runner, test_case in runners:
runner_name_without_extension = Path(runner).stem
runner_elf_file = f"{runner_name_without_extension}.elf"
output.append(
f"add_executable({runner_elf_file} {runner_name_without_extension}.c {test_case} {files_to_include})"
)
output.append(f"target_link_libraries({runner_elf_file} c lvgl unity)")
output.append(
f"target_compile_definitions({runner_elf_file} PRIVATE LV_BUILD_TEST LV_BUILD_TEST_PERF)",
)
output.append(
f"target_include_directories({runner_elf_file} PRIVATE ${{CMAKE_CURRENT_SOURCE_DIR}}/../unity)"
)
output.append(f"target_compile_options({runner_elf_file} PRIVATE -ggdb)")
write_lines_to_file(path, output)
def copy_unity(target_folder: str) -> None:
"""
Copies the unity source files to `target_folder`
"""
print(f"Generating {target_folder}")
unity_src_dir = os.path.join(lvgl_test_dir, "unity")
shutil.copytree(unity_src_dir, target_folder, dirs_exist_ok=True)
def copy_lvgl_test_files(target_folder: str) -> None:
"""
Copies the lvgl test source files `target_folder`
"""
print(f"Generating {target_folder}")
for src in LVGL_TEST_FILES:
dst = os.path.join(target_folder, os.path.basename(src))
shutil.copy(src, dst)
def generate_unity_cmakelists(path: str) -> None:
"""
Generates a CMakeLists.txt for the unity framework
"""
print(f"Generating {path}")
content_lines = [
# Create a static library
"add_library(unity STATIC unity.c)",
# Add the necessary compile definitions in order to build the library correctly
# Setting these to PUBLIC allows other apps that link with this library to use the same compile definitions
# Here we enable LV_BUILD_TEST and LV_BUILD_TEST_PERF so we enable the file contents
# UNITY_INCLUDE_DOUBLE enables macros like `TEST_ASSERT_LESS_OR_EQUAL_DOUBLE`
# UNITY_OUTPUT_COLOR enables coloring the `OK` and the `FAIL` output of the tests
"target_compile_definitions(unity PUBLIC LV_BUILD_TEST LV_BUILD_TEST_PERF UNITY_INCLUDE_DOUBLE UNITY_OUTPUT_COLOR)",
]
write_lines_to_file(path, content_lines)
def generate_so3_usr_cmakelists(path: str) -> None:
"""
Generates the main CMakeLists.txt that will be mounted in `usr/src/CMakeLists.txt`
We need to keep the `init` program as it's the program that will be
responsible for launching every runner
We also add a new subdirectory `test_src` that will be the folder containing
every test runner, and necessary sources for building those runners
(except the lvgl source code)
"""
print(f"Generating {path}")
content_lines = [
"add_subdirectory(unity)",
"add_executable(init.elf init.c)",
"target_link_libraries(init.elf c)",
"add_subdirectory(test_src)",
]
write_lines_to_file(path, content_lines)
def generate_test_runners(
output_folder: str, test_suite: Optional[str]
) -> list[tuple[str, str]]:
"""
Generates performance test runners by creating a runner for each `.c` file
located in the `test_cases/perf` directory.
If `test_suite` is provided, only `.c` files matching the suite will be used.
Otherwise, all `.c` files in the directory are included.
Each generated runner, along with its corresponding `.c` file, will be saved
in the specified `output_folder`.
"""
runner_generator_script = os.path.join(
lvgl_test_dir, "unity", "generate_test_runner.rb"
)
# Get the necessary files in order to generate the runners
# This includes the test cases and the unity config
test_cases = find_c_files(os.path.join(lvgl_test_dir, "src", "test_cases_perf"))
unity_config_path = os.path.join(lvgl_test_dir, "config.yml")
runners = []
print("Generating test case runners")
for src_test_case_path in test_cases:
current_test_suite = Path(src_test_case_path).stem
if test_suite and test_suite not in current_test_suite:
continue
print(f"\t{current_test_suite}")
runner_file_name = current_test_suite + "_runner.c"
test_case_file_name = os.path.basename(src_test_case_path)
test_case_path = os.path.join(output_folder, test_case_file_name)
runner_path = os.path.join(output_folder, runner_file_name)
subprocess.check_call(
[
runner_generator_script,
src_test_case_path,
runner_path,
unity_config_path,
]
)
# Copy the original test case as still need them in the build process
shutil.copy(src_test_case_path, test_case_path)
# Store a tuple of runner - test case so we can generate the cmakelists later
runners.append((runner_file_name, test_case_file_name))
return runners
def generate_files(options_name, test_suite):
"""
Generates every necessary file for running tests inside so3
Everything is built inside a docker container so we need to prepare
every file we need to mount before mounting and running the container
"""
print()
print()
label = "Generating: %s: %s" % (
options_abbrev(options_name),
perf_test_options[options_name]["description"],
)
print("=" * len(label))
print(label)
print("=" * len(label))
options_build_dir = get_build_dir(options_name)
generated_unity_dir = os.path.join(options_build_dir, "unity")
generated_test_src_dir = os.path.join(options_build_dir, "test_src")
create_dir(options_build_dir)
create_dir(generated_test_src_dir)
runners = generate_test_runners(generated_test_src_dir, test_suite)
# Copy lvgl common test files
copy_lvgl_test_files(generated_test_src_dir)
# Copy Unity framework
copy_unity(generated_unity_dir)
# Generate necessary cmakelists
generate_unity_cmakelists(os.path.join(generated_unity_dir, "CMakeLists.txt"))
generate_perf_test_cmakelists(
runners, os.path.join(generated_test_src_dir, "CMakeLists.txt")
)
generate_so3_usr_cmakelists(os.path.join(options_build_dir, "CMakeLists.txt"))
generate_so3_init_commands(runners, os.path.join(options_build_dir, "commands.ini"))
def clean(options_name: str) -> None:
options_build_dir = get_build_dir(options_name)
container_name = get_container_name(options_name)
if os.path.exists(options_build_dir):
shutil.rmtree(options_build_dir)
subprocess.check_call(["docker", "rm", "-f", container_name])
for v in get_docker_volumes(options_name):
subprocess.check_call(["docker", "volume", "remove", "-f", v])
def check_for_success(container_name):
"""
There's no support for return codes when running qemu so we manually check the docker
logs to see if any case failed
"""
stdout = subprocess.getoutput(f"docker logs {container_name}")
for line in stdout.splitlines():
if "FAIL" in line.strip():
return False
return True
def run_tests(
options_name: str,
lv_conf_name: str,
pull: bool,
debug: bool,
debug_port: int,
test_suite: Optional[str],
) -> bool:
"""
Runs the tests by running the docker image associated with `options_name`
while mounting the correct volumes from the previous generated files
"""
def volume(src, dst):
return ["-v", f"{src}:{dst}"]
def so3_usr_src(path):
return f"/so3/usr/src/{path}"
def so3_usr_lib(path):
return f"/so3/usr/lib/{path}"
def so3_usr_out(path):
return f"/so3/usr/out/{path}"
so3_usr_build = f"/so3/usr/build"
persistence_dir = f"/persistence"
container_name = get_container_name(options_name)
build_dir = get_build_dir(options_name)
for v in get_docker_volumes(options_name):
subprocess.check_call(["docker", "volume", "create", v])
unity_dir = os.path.join(build_dir, "unity")
test_src_dir = os.path.join(build_dir, "test_src")
main_cmakelists = os.path.join(build_dir, "CMakeLists.txt")
lvgl_src_dir = os.path.join(lvgl_test_dir, "..", "src")
lv_conf_path = os.path.join(lvgl_test_dir, "src", lv_conf_name)
lvgl_h_path = os.path.join(lvgl_test_dir, "..", "lvgl.h")
lvgl_private_h_path = os.path.join(lvgl_test_dir, "..", "lvgl_private.h")
commands_ini_path = os.path.join(build_dir, "commands.ini")
docker_image_name = perf_test_options[options_name]["image_name"]
volumes = [
# This is necessary in order to create a loop device
# It is also the reason we only support linux for now.
volume("/dev", "/dev"),
# Replace container's lvgl source and lv_conf
volume(lvgl_src_dir, so3_usr_lib("lvgl/src")),
volume(lv_conf_path, so3_usr_lib("lv_conf.h")),
volume(lvgl_h_path, so3_usr_lib("lvgl/lvgl.h")),
volume(lvgl_private_h_path, so3_usr_lib("lvgl/lvgl_private.h")),
# We also need to add the current "lvgl.h" and mount it in the correct path
# As there's a `#include "../../lvgl.h"` in the `unity_support.h` file
volume(lvgl_h_path, "/so3/usr/lvgl.h"),
# Mount the test sources (test cases and runners)
volume(test_src_dir, so3_usr_src("test_src")),
# Mount the test framework
volume(unity_dir, so3_usr_src("unity")),
# Modify the default so3 CMakeLists and commands.ini
volume(main_cmakelists, so3_usr_src("CMakeLists.txt")),
volume(commands_ini_path, so3_usr_out("commands.ini")),
# Cache build and disk folders so we don't regenerate everything in consecutive runs
volume(get_build_cache_volume(options_name), so3_usr_build),
volume(get_disk_cache_volume(options_name), persistence_dir),
]
interactive = "-it" if sys.stdout.isatty() else "-t"
command = ["docker", "run", "--privileged", "--name", container_name, interactive]
print()
print()
label = "Launching: %s: %s" % (
options_abbrev(options_name),
perf_test_options[options_name]["description"],
)
print("=" * len(label))
print(label)
print("=" * len(label))
if pull:
command.append("--pull=always")
if debug:
command.append("-e")
command.append("SO3_USR_DEBUG=1")
command.append("-p")
command.append(f"{debug_port}:1234")
for v in volumes:
command.extend(v)
command.append(docker_image_name)
try:
subprocess.check_call(command)
except subprocess.CalledProcessError:
subprocess.check_call(["docker", "rm", "-f", container_name])
return False
success = check_for_success(container_name)
# We can't use the `docker run --rm` syntax because we need access to the docker container
# after it exits in order to check for the success status of the run
subprocess.check_call(["docker", "rm", "-f", container_name])
return success
if __name__ == "__main__":
sys.exit(main())

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Some files were not shown because too many files have changed in this diff Show More