From cc774111bf20ce9109214ed5bd64fe72c5fd7d45 Mon Sep 17 00:00:00 2001 From: Renz Bagaporo Date: Mon, 27 Aug 2018 10:48:16 +0800 Subject: [PATCH] cmake: Add support for test build --- .gitlab-ci.yml | 36 ++- components/app_trace/test/CMakeLists.txt | 6 + components/app_update/test/CMakeLists.txt | 6 + components/bootloader/project_include.cmake | 3 +- .../bootloader_support/test/CMakeLists.txt | 6 + components/bt/test/CMakeLists.txt | 8 + components/cxx/test/CMakeLists.txt | 6 + components/driver/test/CMakeLists.txt | 6 + components/esp32/CMakeLists.txt | 4 + components/esp32/test/CMakeLists.txt | 30 ++ components/esp_ringbuf/test/CMakeLists.txt | 6 + components/espcoredump/test/CMakeLists.txt | 6 + components/ethernet/test/CMakeLists.txt | 5 + components/expat/test/CMakeLists.txt | 5 + components/fatfs/test/CMakeLists.txt | 8 + components/freertos/test/CMakeLists.txt | 6 + components/heap/test/CMakeLists.txt | 6 + components/http_server/test/CMakeLists.txt | 6 + components/libsodium/CMakeLists.txt | 8 +- components/libsodium/test/CMakeLists.txt | 40 +++ components/mbedtls/test/CMakeLists.txt | 6 + components/newlib/test/CMakeLists.txt | 6 + components/nvs_flash/test/CMakeLists.txt | 6 + .../partition_table/test/CMakeLists.txt | 6 + components/protocomm/test/CMakeLists.txt | 7 + components/pthread/test/CMakeLists.txt | 6 + components/sdmmc/test/CMakeLists.txt | 6 + components/soc/esp32/sources.cmake | 7 +- components/soc/test/CMakeLists.txt | 7 + components/spi_flash/test/CMakeLists.txt | 6 + components/spiffs/test/CMakeLists.txt | 6 + components/ulp/test/CMakeLists.txt | 11 + components/ulp/test/component.mk | 2 +- components/ulp/test/test_ulp_as.c | 10 +- components/vfs/test/CMakeLists.txt | 6 + components/wear_levelling/test/CMakeLists.txt | 8 + components/wpa_supplicant/test/CMakeLists.txt | 6 + docs/en/api-guides/build-system-cmake.rst | 2 + docs/en/api-guides/index.rst | 1 + docs/en/api-guides/unit-tests-cmake.rst | 211 +++++++++++++ docs/zh_CN/api-guides/index.rst | 1 + docs/zh_CN/api-guides/unit-tests-cmake.rst | 248 ++++++++++++++++ tools/ci/check_ut_cmake_make.sh | 19 ++ tools/ci/executable-list.txt | 1 + tools/cmake/components.cmake | 9 +- tools/cmake/idf_functions.cmake | 2 +- tools/cmake/project.cmake | 22 +- tools/cmake/scripts/expand_requirements.cmake | 125 ++++++-- tools/idf.py | 81 +++-- tools/unit-test-app/CMakeLists.txt | 6 + tools/unit-test-app/README.md | 16 +- .../components/unity/CMakeLists.txt | 10 + tools/unit-test-app/idf_ext.py | 279 ++++++++++++++++++ tools/unit-test-app/main/CMakeLists.txt | 4 + 54 files changed, 1290 insertions(+), 66 deletions(-) create mode 100644 components/app_trace/test/CMakeLists.txt create mode 100644 components/app_update/test/CMakeLists.txt create mode 100644 components/bootloader_support/test/CMakeLists.txt create mode 100644 components/bt/test/CMakeLists.txt create mode 100644 components/cxx/test/CMakeLists.txt create mode 100644 components/driver/test/CMakeLists.txt create mode 100644 components/esp32/test/CMakeLists.txt create mode 100644 components/esp_ringbuf/test/CMakeLists.txt create mode 100644 components/espcoredump/test/CMakeLists.txt create mode 100644 components/ethernet/test/CMakeLists.txt create mode 100644 components/expat/test/CMakeLists.txt create mode 100644 components/fatfs/test/CMakeLists.txt create mode 100644 components/freertos/test/CMakeLists.txt create mode 100644 components/heap/test/CMakeLists.txt create mode 100644 components/http_server/test/CMakeLists.txt create mode 100644 components/libsodium/test/CMakeLists.txt create mode 100644 components/mbedtls/test/CMakeLists.txt create mode 100644 components/newlib/test/CMakeLists.txt create mode 100644 components/nvs_flash/test/CMakeLists.txt create mode 100644 components/partition_table/test/CMakeLists.txt create mode 100644 components/protocomm/test/CMakeLists.txt create mode 100644 components/pthread/test/CMakeLists.txt create mode 100644 components/sdmmc/test/CMakeLists.txt create mode 100644 components/soc/test/CMakeLists.txt create mode 100644 components/spi_flash/test/CMakeLists.txt create mode 100644 components/spiffs/test/CMakeLists.txt create mode 100644 components/ulp/test/CMakeLists.txt create mode 100644 components/vfs/test/CMakeLists.txt create mode 100644 components/wear_levelling/test/CMakeLists.txt create mode 100644 components/wpa_supplicant/test/CMakeLists.txt create mode 100644 docs/en/api-guides/unit-tests-cmake.rst create mode 100644 docs/zh_CN/api-guides/unit-tests-cmake.rst create mode 100755 tools/ci/check_ut_cmake_make.sh create mode 100644 tools/unit-test-app/CMakeLists.txt create mode 100644 tools/unit-test-app/components/unity/CMakeLists.txt create mode 100644 tools/unit-test-app/idf_ext.py create mode 100644 tools/unit-test-app/main/CMakeLists.txt diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 383dbd8d9..0f90f5cb3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,6 +24,7 @@ variables: GIT_STRATEGY: fetch GIT_SUBMODULE_STRATEGY: none + UNIT_TEST_BUILD_SYSTEM: make # IDF environment IDF_PATH: "$CI_PROJECT_DIR" @@ -172,11 +173,24 @@ build_esp_idf_tests: - components/idf_test/unit_test/CIConfigs/*.yml expire_in: 2 days script: - - cd tools/unit-test-app - - MAKEFLAGS= make help # make sure kconfig tools are built in single process - - make ut-clean-all-configs + - export PATH="$IDF_PATH/tools:$PATH" + - cd $CI_PROJECT_DIR/tools/unit-test-app - export EXTRA_CFLAGS="-Werror -Werror=deprecated-declarations" - export EXTRA_CXXFLAGS=${EXTRA_CFLAGS} + # Build with CMake first + - idf.py ut-clean-all-configs + - idf.py ut-build-all-configs + - python tools/UnitTestParser.py + # Check if test demands CMake or Make built binaries. If CMake leave the built artifacts as is then exit. + - if [ "$UNIT_TEST_BUILD_SYSTEM" == "cmake" ]; then exit 0; fi + # If Make, delete the CMake built artifacts + - rm -rf builds output sdkconfig + - rm -rf components/idf_test/unit_test/TestCaseAll.yml + - rm -rf components/idf_test/unit_test/CIConfigs/*.yml + # Then build with Make + - cd $CI_PROJECT_DIR/tools/unit-test-app + - MAKEFLAGS= make help # make sure kconfig tools are built in single process + - make ut-clean-all-configs - make ut-build-all-configs - python tools/UnitTestParser.py @@ -623,6 +637,20 @@ check_examples_cmake_make: script: - tools/ci/check_examples_cmake_make.sh +check_ut_cmake_make: + stage: check + image: $CI_DOCKER_REGISTRY/esp32-ci-env$BOT_DOCKER_IMAGE_TAG + tags: + - build + except: + - master + - /^release\/v/ + - /^v\d+\.\d+(\.\d+)?($|-)/ + dependencies: [] + before_script: *do_nothing_before + script: + - tools/ci/check_ut_cmake_make.sh + check_submodule_sync: <<: *check_job_template variables: @@ -1577,4 +1605,4 @@ IT_015_01: <<: *test_template tags: - ESP32_IDF - - SSC_T2_4 + - SSC_T2_4 \ No newline at end of file diff --git a/components/app_trace/test/CMakeLists.txt b/components/app_trace/test/CMakeLists.txt new file mode 100644 index 000000000..884ca8b6d --- /dev/null +++ b/components/app_trace/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity) + +register_component() \ No newline at end of file diff --git a/components/app_update/test/CMakeLists.txt b/components/app_update/test/CMakeLists.txt new file mode 100644 index 000000000..2e3975780 --- /dev/null +++ b/components/app_update/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity app_update bootloader_support nvs_flash) + +register_component() \ No newline at end of file diff --git a/components/bootloader/project_include.cmake b/components/bootloader/project_include.cmake index cdb18a9fa..d59689820 100644 --- a/components/bootloader/project_include.cmake +++ b/components/bootloader/project_include.cmake @@ -17,7 +17,8 @@ externalproject_add(bootloader # TODO: support overriding the bootloader in COMPONENT_PATHS SOURCE_DIR "${IDF_PATH}/components/bootloader/subproject" BINARY_DIR "${bootloader_build_dir}" - CMAKE_ARGS -DSDKCONFIG=${SDKCONFIG} -DIDF_PATH=${IDF_PATH} + CMAKE_ARGS -DSDKCONFIG=${SDKCONFIG} -DIDF_PATH=${IDF_PATH} -DEXTRA_COMPONENT_DIRS=${COMPONENT_DIRS} + -DTESTS_ALL=0 -DTEST_COMPONENTS="" INSTALL_COMMAND "" BUILD_ALWAYS 1 # no easy way around this... BUILD_BYPRODUCTS ${bootloader_binary_files} diff --git a/components/bootloader_support/test/CMakeLists.txt b/components/bootloader_support/test/CMakeLists.txt new file mode 100644 index 000000000..587c81609 --- /dev/null +++ b/components/bootloader_support/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity bootloader_support app_update) + +register_component() \ No newline at end of file diff --git a/components/bt/test/CMakeLists.txt b/components/bt/test/CMakeLists.txt new file mode 100644 index 000000000..c8c2dda4b --- /dev/null +++ b/components/bt/test/CMakeLists.txt @@ -0,0 +1,8 @@ +if(CONFIG_BT_ENABLED) + set(COMPONENT_SRCDIRS ".") + set(COMPONENT_ADD_INCLUDEDIRS ".") +endif() + +set(COMPONENT_REQUIRES unity nvs_flash bt) + +register_component() diff --git a/components/cxx/test/CMakeLists.txt b/components/cxx/test/CMakeLists.txt new file mode 100644 index 000000000..884ca8b6d --- /dev/null +++ b/components/cxx/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity) + +register_component() \ No newline at end of file diff --git a/components/driver/test/CMakeLists.txt b/components/driver/test/CMakeLists.txt new file mode 100644 index 000000000..05ec29c31 --- /dev/null +++ b/components/driver/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity driver nvs_flash) + +register_component() \ No newline at end of file diff --git a/components/esp32/CMakeLists.txt b/components/esp32/CMakeLists.txt index c1b7c6e38..efb675102 100644 --- a/components/esp32/CMakeLists.txt +++ b/components/esp32/CMakeLists.txt @@ -144,4 +144,8 @@ else() endif() + # Enable dynamic esp_timer overflow value if building unit tests + if(NOT "${BUILD_TEST_COMPONENTS}" EQUAL "") + add_definitions(-DESP_TIMER_DYNAMIC_OVERFLOW_VAL) + endif() endif() diff --git a/components/esp32/test/CMakeLists.txt b/components/esp32/test/CMakeLists.txt new file mode 100644 index 000000000..3c6373fb9 --- /dev/null +++ b/components/esp32/test/CMakeLists.txt @@ -0,0 +1,30 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ". ${CMAKE_CURRENT_BINARY_DIR}") + +set(COMPONENT_REQUIRES unity nvs_flash ulp) + +register_component() + +add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test_tjpgd_logo.h" + COMMAND xxd -i "logo.jpg" "${CMAKE_CURRENT_BINARY_DIR}/test_tjpgd_logo.h" + WORKING_DIRECTORY ${COMPONENT_PATH} + DEPENDS "${CMAKE_CURRENT_LIST_DIR}/logo.jpg") + +# Calculate MD5 value of header file esp_wifi_os_adapter.h +execute_process(COMMAND md5sum ${IDF_PATH}/components/esp32/include/esp_wifi_os_adapter.h + COMMAND cut -c 1-7 + OUTPUT_VARIABLE WIFI_OS_ADAPTER_MD5 + OUTPUT_STRIP_TRAILING_WHITESPACE) + +# Calculate MD5 value of header file esp_wifi_crypto_types.h +execute_process(COMMAND md5sum ${IDF_PATH}/components/esp32/include/esp_wifi_crypto_types.h + COMMAND cut -c 1-7 + OUTPUT_VARIABLE WIFI_CRYPTO_MD5 + OUTPUT_STRIP_TRAILING_WHITESPACE) + +add_definitions(-DWIFI_OS_ADAPTER_MD5=\"${WIFI_OS_ADAPTER_MD5}\") +add_definitions(-DWIFI_CRYPTO_MD5=\"${WIFI_CRYPTO_MD5}\") + +add_custom_target(esp32_test_logo DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/test_tjpgd_logo.h") + +add_dependencies(${COMPONENT_NAME} esp32_test_logo) \ No newline at end of file diff --git a/components/esp_ringbuf/test/CMakeLists.txt b/components/esp_ringbuf/test/CMakeLists.txt new file mode 100644 index 000000000..1fa278468 --- /dev/null +++ b/components/esp_ringbuf/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity) + +register_component() diff --git a/components/espcoredump/test/CMakeLists.txt b/components/espcoredump/test/CMakeLists.txt new file mode 100644 index 000000000..884ca8b6d --- /dev/null +++ b/components/espcoredump/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity) + +register_component() \ No newline at end of file diff --git a/components/ethernet/test/CMakeLists.txt b/components/ethernet/test/CMakeLists.txt new file mode 100644 index 000000000..d5d44577a --- /dev/null +++ b/components/ethernet/test/CMakeLists.txt @@ -0,0 +1,5 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") +set(COMPONENT_REQUIRES unity ethernet) + +register_component() \ No newline at end of file diff --git a/components/expat/test/CMakeLists.txt b/components/expat/test/CMakeLists.txt new file mode 100644 index 000000000..be2a82217 --- /dev/null +++ b/components/expat/test/CMakeLists.txt @@ -0,0 +1,5 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") +set(COMPONENT_REQUIRES unity expat) + +register_component() diff --git a/components/fatfs/test/CMakeLists.txt b/components/fatfs/test/CMakeLists.txt new file mode 100644 index 000000000..26338ce12 --- /dev/null +++ b/components/fatfs/test/CMakeLists.txt @@ -0,0 +1,8 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity vfs fatfs) + +set(COMPONENT_EMBED_TXTFILES fatfs.img) + +register_component() \ No newline at end of file diff --git a/components/freertos/test/CMakeLists.txt b/components/freertos/test/CMakeLists.txt new file mode 100644 index 000000000..884ca8b6d --- /dev/null +++ b/components/freertos/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity) + +register_component() \ No newline at end of file diff --git a/components/heap/test/CMakeLists.txt b/components/heap/test/CMakeLists.txt new file mode 100644 index 000000000..884ca8b6d --- /dev/null +++ b/components/heap/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity) + +register_component() \ No newline at end of file diff --git a/components/http_server/test/CMakeLists.txt b/components/http_server/test/CMakeLists.txt new file mode 100644 index 000000000..1c577575a --- /dev/null +++ b/components/http_server/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity http_server) + +register_component() \ No newline at end of file diff --git a/components/libsodium/CMakeLists.txt b/components/libsodium/CMakeLists.txt index aa39b0047..4ee2f20b3 100644 --- a/components/libsodium/CMakeLists.txt +++ b/components/libsodium/CMakeLists.txt @@ -166,4 +166,10 @@ if(GCC_NOT_5_2_0) PROPERTIES COMPILE_FLAGS -Wno-implicit-fallthrough ) -endif() \ No newline at end of file +endif() + +set_source_files_properties( + ${SRC}/randombytes/randombytes.c + PROPERTIES COMPILE_FLAGS + -DRANDOMBYTES_DEFAULT_IMPLEMENTATION +) diff --git a/components/libsodium/test/CMakeLists.txt b/components/libsodium/test/CMakeLists.txt new file mode 100644 index 000000000..76ac24d2f --- /dev/null +++ b/components/libsodium/test/CMakeLists.txt @@ -0,0 +1,40 @@ +if(TESTS_ALL EQUAL 1) + message("not linking libsodium tests, use '-T libsodium' to test it") +else() + get_filename_component(LS_TESTDIR "${CMAKE_CURRENT_LIST_DIR}/../libsodium/test/default" ABSOLUTE) + + set(COMPONENT_ADD_INCLUDEDIRS "." "${LS_TESTDIR}/../quirks") + + set(COMPONENT_REQUIRES unity libsodium) + + set(TEST_CASES "chacha20;aead_chacha20poly1305;box;box2;ed25519_convert;sign;hash") + + foreach(test_case ${TEST_CASES}) + file(GLOB test_case_file "${LS_TESTDIR}/${test_case}.c") + list(APPEND TEST_CASES_FILES ${test_case_file}) + endforeach() + + set(COMPONENT_SRCS "${TEST_CASES_FILES};test_sodium.c") + + register_component() + + # The libsodium test suite is designed to be run each test case as an executable on a desktop computer and uses + # filesytem to write & then compare contents of each file. + # + # For now, use their "BROWSER_TEST" mode with these hacks so that + # multiple test cases can be combined into one ELF file. + # + # Run each test case from test_sodium.c as CASENAME_xmain(). + foreach(test_case_file ${TEST_CASES_FILES}) + get_filename_component(test_case ${test_case_file} NAME_WE) + set_source_files_properties(${test_case_file} + PROPERTIES COMPILE_FLAGS + # This would generate 'warning "main" redefined' warnings at runtime, which are + # silenced here. Only other solution involves patching libsodium's cmptest.h. + "-Dxmain=${test_case}_xmain -Dmain=${test_case}_main -Wp,-w") + endforeach() + + # this seems odd, but it prevents the libsodium test harness from + # trying to write to a file! + add_definitions(-DBROWSER_TESTS) +endif() \ No newline at end of file diff --git a/components/mbedtls/test/CMakeLists.txt b/components/mbedtls/test/CMakeLists.txt new file mode 100644 index 000000000..8e7ae1f8d --- /dev/null +++ b/components/mbedtls/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity mbedtls) + +register_component() \ No newline at end of file diff --git a/components/newlib/test/CMakeLists.txt b/components/newlib/test/CMakeLists.txt new file mode 100644 index 000000000..884ca8b6d --- /dev/null +++ b/components/newlib/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity) + +register_component() \ No newline at end of file diff --git a/components/nvs_flash/test/CMakeLists.txt b/components/nvs_flash/test/CMakeLists.txt new file mode 100644 index 000000000..84baa23fa --- /dev/null +++ b/components/nvs_flash/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity nvs_flash bootloader_support) + +register_component() diff --git a/components/partition_table/test/CMakeLists.txt b/components/partition_table/test/CMakeLists.txt new file mode 100644 index 000000000..884ca8b6d --- /dev/null +++ b/components/partition_table/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity) + +register_component() \ No newline at end of file diff --git a/components/protocomm/test/CMakeLists.txt b/components/protocomm/test/CMakeLists.txt new file mode 100644 index 000000000..f25cb01a1 --- /dev/null +++ b/components/protocomm/test/CMakeLists.txt @@ -0,0 +1,7 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") +set(COMPONENT_PRIV_INCLUDEDIRS "../proto-c/") + +set(COMPONENT_REQUIRES unity mbedtls protocomm protobuf-c) + +register_component() diff --git a/components/pthread/test/CMakeLists.txt b/components/pthread/test/CMakeLists.txt new file mode 100644 index 000000000..7786bd3cd --- /dev/null +++ b/components/pthread/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity pthread) + +register_component() \ No newline at end of file diff --git a/components/sdmmc/test/CMakeLists.txt b/components/sdmmc/test/CMakeLists.txt new file mode 100644 index 000000000..885cda77a --- /dev/null +++ b/components/sdmmc/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity sdmmc) + +register_component() \ No newline at end of file diff --git a/components/soc/esp32/sources.cmake b/components/soc/esp32/sources.cmake index 25e2061af..4f6e2e3af 100644 --- a/components/soc/esp32/sources.cmake +++ b/components/soc/esp32/sources.cmake @@ -11,4 +11,9 @@ set(SOC_SRCS "cpu_util.c" "sdio_slave_periph.c" "sdmmc_periph.c" "soc_memory_layout.c" - "spi_periph.c") \ No newline at end of file + "spi_periph.c") + +if(NOT CMAKE_BUILD_EARLY_EXPANSION) + set_source_files_properties("esp32/rtc_clk.c" PROPERTIES + COMPILE_FLAGS "-fno-jump-tables -fno-tree-switch-conversion") +endif() diff --git a/components/soc/test/CMakeLists.txt b/components/soc/test/CMakeLists.txt new file mode 100644 index 000000000..f4e89ade2 --- /dev/null +++ b/components/soc/test/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOC_NAME esp32) +set(COMPONENT_SRCDIRS "../${SOC_NAME}/test") +set(COMPONENT_ADD_INCLUDEDIRS "../${SOC_NAME}/test") + +set(COMPONENT_REQUIRES unity) + +register_component() \ No newline at end of file diff --git a/components/spi_flash/test/CMakeLists.txt b/components/spi_flash/test/CMakeLists.txt new file mode 100644 index 000000000..8e8ee6a92 --- /dev/null +++ b/components/spi_flash/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity spi_flash bootloader_support app_update) + +register_component() \ No newline at end of file diff --git a/components/spiffs/test/CMakeLists.txt b/components/spiffs/test/CMakeLists.txt new file mode 100644 index 000000000..860ab6df4 --- /dev/null +++ b/components/spiffs/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity spiffs) + +register_component() \ No newline at end of file diff --git a/components/ulp/test/CMakeLists.txt b/components/ulp/test/CMakeLists.txt new file mode 100644 index 000000000..3b0f9efa4 --- /dev/null +++ b/components/ulp/test/CMakeLists.txt @@ -0,0 +1,11 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity ulp soc) + +register_component() + +set(ULP_APP_NAME ulp_test_app) +set(ULP_S_SOURCES "ulp/test_jumps.S") +set(ULP_EXP_DEP_SRCS "test_ulp_as.c") +include(${IDF_PATH}/components/ulp/component_ulp_common.cmake) diff --git a/components/ulp/test/component.mk b/components/ulp/test/component.mk index 41228b257..84d7727d7 100644 --- a/components/ulp/test/component.mk +++ b/components/ulp/test/component.mk @@ -1,4 +1,4 @@ -ULP_APP_NAME = ulp_test +ULP_APP_NAME = ulp_test_app ULP_S_SOURCES = $(addprefix $(COMPONENT_PATH)/ulp/, \ test_jumps.S \ diff --git a/components/ulp/test/test_ulp_as.c b/components/ulp/test/test_ulp_as.c index b0a080717..12cb649f9 100644 --- a/components/ulp/test/test_ulp_as.c +++ b/components/ulp/test/test_ulp_as.c @@ -2,17 +2,17 @@ #include "unity.h" #include "soc/rtc_cntl_reg.h" #include "esp32/ulp.h" -#include "ulp_test.h" +#include "ulp_test_app.h" -extern const uint8_t ulp_test_bin_start[] asm("_binary_ulp_test_bin_start"); -extern const uint8_t ulp_test_bin_end[] asm("_binary_ulp_test_bin_end"); +extern const uint8_t ulp_test_app_bin_start[] asm("_binary_ulp_test_app_bin_start"); +extern const uint8_t ulp_test_app_bin_end[] asm("_binary_ulp_test_app_bin_end"); TEST_CASE("jumps condition", "[ulp]") { - esp_err_t err = ulp_load_binary(0, ulp_test_bin_start, - (ulp_test_bin_end - ulp_test_bin_start) / sizeof(uint32_t)); + esp_err_t err = ulp_load_binary(0, ulp_test_app_bin_start, + (ulp_test_app_bin_end - ulp_test_app_bin_start) / sizeof(uint32_t)); TEST_ESP_OK(err); REG_CLR_BIT(RTC_CNTL_INT_RAW_REG, RTC_CNTL_ULP_CP_INT_RAW); diff --git a/components/vfs/test/CMakeLists.txt b/components/vfs/test/CMakeLists.txt new file mode 100644 index 000000000..200f60572 --- /dev/null +++ b/components/vfs/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity vfs fatfs spiffs) + +register_component() \ No newline at end of file diff --git a/components/wear_levelling/test/CMakeLists.txt b/components/wear_levelling/test/CMakeLists.txt new file mode 100644 index 000000000..b75e94238 --- /dev/null +++ b/components/wear_levelling/test/CMakeLists.txt @@ -0,0 +1,8 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity wear_levelling) + +set(COMPONENT_EMBED_FILES test_partition_v1.bin) + +register_component() \ No newline at end of file diff --git a/components/wpa_supplicant/test/CMakeLists.txt b/components/wpa_supplicant/test/CMakeLists.txt new file mode 100644 index 000000000..28ca784c0 --- /dev/null +++ b/components/wpa_supplicant/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity wpa_supplicant mbedtls) + +register_component() diff --git a/docs/en/api-guides/build-system-cmake.rst b/docs/en/api-guides/build-system-cmake.rst index f0416089b..b6888870a 100644 --- a/docs/en/api-guides/build-system-cmake.rst +++ b/docs/en/api-guides/build-system-cmake.rst @@ -219,6 +219,7 @@ Project CMakeLists File Each project has a single top-level ``CMakeLists.txt`` file that contains build settings for the entire project. By default, the project CMakeLists can be quite minimal. + Minimal Example CMakeLists -------------------------- @@ -796,6 +797,7 @@ Here is an example minimal "pure CMake" component CMakeLists file for a componen - This file is quite simple as there are not a lot of source files. For components with a large number of files, the globbing behaviour of ESP-IDF's component logic can make the component CMakeLists style simpler.) - Any time a component adds a library target with the component name, the ESP-IDF build system will automatically add this to the build, expose public include directories, etc. If a component wants to add a library target with a different name, dependencies will need to be added manually via CMake commands. +.. _cmake-file-globbing: File Globbing & Incremental Builds ================================== diff --git a/docs/en/api-guides/index.rst b/docs/en/api-guides/index.rst index b5352171d..cf1044686 100644 --- a/docs/en/api-guides/index.rst +++ b/docs/en/api-guides/index.rst @@ -22,6 +22,7 @@ API Guides ULP Coprocessor ULP Coprocessor (CMake) Unit Testing + Unit Testing (CMake) Application Level Tracing Console Component ROM debug console diff --git a/docs/en/api-guides/unit-tests-cmake.rst b/docs/en/api-guides/unit-tests-cmake.rst new file mode 100644 index 000000000..05a5bf343 --- /dev/null +++ b/docs/en/api-guides/unit-tests-cmake.rst @@ -0,0 +1,211 @@ +Unit Testing in ESP32 (CMake) +============================= + +.. include:: ../cmake-warning.rst + +ESP-IDF comes with a unit test app based on Unity - unit test framework. Unit tests are integrated in the ESP-IDF repository and are placed in ``test`` subdirectory of each component respectively. + +Add normal test cases +--------------------- + +Unit tests are added in the ``test`` subdirectory of the respective component. +Tests are added in C files, a single C file can include multiple test cases. +Test files start with the word "test". + +The test file should include unity.h and the header for the C module to be tested. + +Tests are added in a function in the C file as follows:: + + TEST_CASE("test name", "[module name]" + { + // Add test here + } + +First argument is a descriptive name for the test, second argument is an identifier in square brackets. +Identifiers are used to group related test, or tests with specific properties. + +There is no need to add a main function with ``UNITY_BEGIN()`` and ``​UNITY_END()`` in each test case. +``unity_platform.c`` will run ``UNITY_BEGIN()``, run the tests cases, and then call ``​UNITY_END()``. + +The ``test`` subdirectory should contain a :ref:`component CMakeLists.txt `, since they are themselves, +components. ESP-IDF uses the test framework ``unity`` and should be specified as a requirement for the component. Normally, components +:ref:`should list their sources manually `; for component tests however, this requirement is relaxed and the +use of ``COMPONENT_SRCDIRS`` is advised. + +Overall, the minimal ``test`` subdirectory CMakeLists.txt file may look like as follows: + +.. code:: cmake + + set(COMPONENT_SRCDIRS ".") + set(COMPONENT_ADD_INCLUDEDIRS ".") + set(COMPONENT_REQUIRES unity) + + register_component() + + +See http://www.throwtheswitch.org/unity for more information about writing tests in Unity. + + +Add multiple devices test cases +------------------------------- + +The normal test cases will be executed on one DUT (Device Under Test). Components need to communicate with each other (like GPIO, SPI ...) can't be tested with normal test cases. +Multiple devices test cases support writing and running test with multiple DUTs. + +Here's an example of multiple devices test case:: + + void gpio_master_test() + { + gpio_config_t slave_config = { + .pin_bit_mask = 1 << MASTER_GPIO_PIN, + .mode = GPIO_MODE_INPUT, + }; + gpio_config(&slave_config); + unity_wait_for_signal("output high level"); + TEST_ASSERT(gpio_get_level(MASTER_GPIO_PIN) == 1); + } + + void gpio_slave_test() + { + gpio_config_t master_config = { + .pin_bit_mask = 1 << SLAVE_GPIO_PIN, + .mode = GPIO_MODE_OUTPUT, + }; + gpio_config(&master_config); + gpio_set_level(SLAVE_GPIO_PIN, 1); + unity_send_signal("output high level"); + } + + TEST_CASE_MULTIPLE_DEVICES("gpio multiple devices test example", "[driver]", gpio_master_test, gpio_slave_test); + + +The macro ``TEST_CASE_MULTIPLE_DEVICES`` is used to declare multiple devices test cases. +First argument is test case name, second argument is test case description. +From the third argument, upto 5 test functions can be defined, each function will be the entry point of tests running on each DUT. + +Running test cases from different DUTs could require synchronizing between DUTs. We provide ``unity_wait_for_signal`` and ``unity_send_signal`` to support synchronizing with UART. +As the secnario in the above example, slave should get GPIO level after master set level. DUT UART console will prompt and requires user interaction: + +DUT1 (master) console:: + + Waiting for signal: [output high level]! + Please press "Enter" key to once any board send this signal. + +DUT2 (slave) console:: + + Send signal: [output high level]! + +Once the signal is set from DUT2, you need to press "Enter" on DUT1, then DUT1 unblocks from ``unity_wait_for_signal`` and starts to change GPIO level. + + +Add multiple stages test cases +------------------------------- + +The normal test cases are expected to finish without reset (or only need to check if reset happens). Sometimes we want to run some specific test after certain kinds of reset. +For example, we want to test if reset reason is correct after wakeup from deep sleep. We need to create deep sleep reset first and then check the reset reason. +To support this, we can define multiple stages test case, to group a set of test functions together:: + + static void trigger_deepsleep(void) + { + esp_sleep_enable_timer_wakeup(2000); + esp_deep_sleep_start(); + } + + void check_deepsleep_reset_reason() + { + RESET_REASON reason = rtc_get_reset_reason(0); + TEST_ASSERT(reason == DEEPSLEEP_RESET); + } + + TEST_CASE_MULTIPLE_STAGES("reset reason check for deepsleep", "[esp32]", trigger_deepsleep, check_deepsleep_reset_reason); + +Multiple stages test cases present a group of test functions to users. It need user interactions (select case and select different stages) to run the case. + + +Building unit test app +---------------------- + +Follow the setup instructions in the top-level esp-idf README. +Make sure that IDF_PATH environment variable is set to point to the path of esp-idf top-level directory. + +Change into tools/unit-test-app directory to configure and build it: + +* `idf.py menuconfig` - configure unit test app. + +* `idf.py build -T all` - build unit test app with tests for each component having tests in the ``test`` subdirectory. +* `idf.py build -T xxx` - build unit test app with tests for specific components. +* `idf.py build -T all -E xxx` - build unit test app with all unit tests, except for unit tests of some components. (For instance: `idf.py build -T all -E ulp mbedtls` - build all unit tests exludes ulp and mbedtls components). + +When the build finishes, it will print instructions for flashing the chip. You can simply run ``idf.py flash`` to flash all build output. + +You can also run ``idf.py flash -T all`` or ``idf.py flash -T xxx`` to build and flash. Everything needed will be rebuilt automatically before flashing. + +Use menuconfig to set the serial port for flashing. + +Running unit tests +------------------ + +After flashing reset the ESP32 and it will boot the unit test app. + +When unit test app is idle, press "Enter" will make it print test menu with all available tests:: + + Here's the test menu, pick your combo: + (1) "esp_ota_begin() verifies arguments" [ota] + (2) "esp_ota_get_next_update_partition logic" [ota] + (3) "Verify bootloader image in flash" [bootloader_support] + (4) "Verify unit test app image" [bootloader_support] + (5) "can use new and delete" [cxx] + (6) "can call virtual functions" [cxx] + (7) "can use static initializers for non-POD types" [cxx] + (8) "can use std::vector" [cxx] + (9) "static initialization guards work as expected" [cxx] + (10) "global initializers run in the correct order" [cxx] + (11) "before scheduler has started, static initializers work correctly" [cxx] + (12) "adc2 work with wifi" [adc] + (13) "gpio master/slave test example" [ignore][misc][test_env=UT_T2_1][multi_device] + (1) "gpio_master_test" + (2) "gpio_slave_test" + (14) "SPI Master clockdiv calculation routines" [spi] + (15) "SPI Master test" [spi][ignore] + (16) "SPI Master test, interaction of multiple devs" [spi][ignore] + (17) "SPI Master no response when switch from host1 (HSPI) to host2 (VSPI)" [spi] + (18) "SPI Master DMA test, TX and RX in different regions" [spi] + (19) "SPI Master DMA test: length, start, not aligned" [spi] + (20) "reset reason check for deepsleep" [esp32][test_env=UT_T2_1][multi_stage] + (1) "trigger_deepsleep" + (2) "check_deepsleep_reset_reason" + +Normal case will print the case name and description. Master slave cases will also print the sub-menu (the registered test function names). + +Test cases can be run by inputting one of the following: + +- Test case name in quotation marks to run a single test case + +- Test case index to run a single test case + +- Module name in square brackets to run all test cases for a specific module + +- An asterisk to run all test cases + +``[multi_device]`` and ``[multi_stage]`` tags tell the test runner whether a test case is a multiple devices or multiple stages test case. +These tags are automatically added by ```TEST_CASE_MULTIPLE_STAGES`` and ``TEST_CASE_MULTIPLE_DEVICES`` macros. + +After you select a multiple devices test case, it will print sub menu:: + + Running gpio master/slave test example... + gpio master/slave test example + (1) "gpio_master_test" + (2) "gpio_slave_test" + +You need to input number to select the test running on the DUT. + +Similar to multiple devices test cases, multiple stages test cases will also print sub menu:: + + Running reset reason check for deepsleep... + reset reason check for deepsleep + (1) "trigger_deepsleep" + (2) "check_deepsleep_reset_reason" + +First time you execute this case, input ``1`` to run first stage (trigger deepsleep). +After DUT is rebooted and able to run test cases, select this case again and input ``2`` to run the second stage. +The case only passes if the last stage passes and all previous stages trigger reset. diff --git a/docs/zh_CN/api-guides/index.rst b/docs/zh_CN/api-guides/index.rst index 9b6e8168e..e4f750467 100644 --- a/docs/zh_CN/api-guides/index.rst +++ b/docs/zh_CN/api-guides/index.rst @@ -22,6 +22,7 @@ API 指南 ULP Coprocessor ULP Coprocessor (CMake) 单元测试 + 单元测试 (CMake) Application Level Tracing Console Component ROM debug console diff --git a/docs/zh_CN/api-guides/unit-tests-cmake.rst b/docs/zh_CN/api-guides/unit-tests-cmake.rst new file mode 100644 index 000000000..dfd4fb6d7 --- /dev/null +++ b/docs/zh_CN/api-guides/unit-tests-cmake.rst @@ -0,0 +1,248 @@ +ESP32 中的单元测试 (CMake) +========================== + +ESP-IDF +中附带了一个基于 ``Unity`` 的单元测试应用程序框架,且所有的单元测试用例分别保存在 +ESP-IDF 仓库中每个组件的 ``test`` 子目录中。 + +添加常规测试用例 +---------------- + +单元测试被添加在相应组件的 ``test`` 子目录中,测试用例写在 C 文件中,一个 +C 文件可以包含多个测试用例。测试文件的名字要以 “test” 开头。 + +测试文件需要包含 ``unity.h`` 头文件,此外还需要包含待测试 C +模块需要的头文件。 + +测试用例需要通过 C 文件中特定的函数来添加,如下所示: + +.. code:: c + + TEST_CASE("test name", "[module name]" + { + // 在这里添加测试用例 + } + +- 第一个参数是字符串,用来描述当前测试。 + +- 第二个参数是字符串,用方括号中的标识符来表示,标识符用来对相关测试或具有特定属性的测试进行分组。 + +没有必要在每个测试用例中使用 ``UNITY_BEGIN()`` 和 ``UNITY_END()`` +来声明主函数的区域, ``unity_platform.c`` 会自动调用 +``UNITY_BEGIN()``\ , 然后运行测试用例,最后调用 ``UNITY_END()``\ 。 + +``test`` 子目录需要包含 :ref:`组件 CMakeLists.txt `,因为他们本身就是一种组件。ESP-IDF 使用了 +``unity`` 测试框架,需要将其指定为组件的依赖项。通常,组件 +:ref:`需要手动指定待编译的源文件 `;但是,对于测试组件来说,这个要求被放宽了,仅仅是建议使用 “COMPONENT_SRCDIRS”。 + +总的来说,``test`` 子目录下最简单的 CMakeLists.txt 文件可能如下所示: + +.. code:: cmake + + set(COMPONENT_SRCDIRS ".") + set(COMPONENT_ADD_INCLUDEDIRS ".") + set(COMPONENT_REQUIRES unity) + + register_component() + +更多关于如何在 Unity 下编写测试用例的信息,请查阅 +http://www.throwtheswitch.org/unity 。 + + +添加多设备测试用例 +------------------ + +常规测试用例会在一个 DUT(Device Under +Test,在试设备)上执行,那些需要互相通信的组件(比如 +GPIO,SPI...)不能使用常规测试用例进行测试。多设备测试用例支持使用多个 +DUT 进行写入和运行测试。 + +以下是一个多设备测试用例: + +.. code:: c + + void gpio_master_test() + { + gpio_config_t slave_config = { + .pin_bit_mask = 1 << MASTER_GPIO_PIN, + .mode = GPIO_MODE_INPUT, + }; + gpio_config(&slave_config); + unity_wait_for_signal("output high level"); + TEST_ASSERT(gpio_get_level(MASTER_GPIO_PIN) == 1); + } + + void gpio_slave_test() + { + gpio_config_t master_config = { + .pin_bit_mask = 1 << SLAVE_GPIO_PIN, + .mode = GPIO_MODE_OUTPUT, + }; + gpio_config(&master_config); + gpio_set_level(SLAVE_GPIO_PIN, 1); + unity_send_signal("output high level"); + } + + TEST_CASE_MULTIPLE_DEVICES("gpio multiple devices test example", "[driver]", gpio_master_test, gpio_slave_test); + +宏 ``TEST_CASE_MULTIPLE_DEVICES`` 用来声明多设备测试用例, + +- 第一个参数指定测试用例的名字。 + +- 第二个参数是测试用例的描述。 + +- 从第三个参数开始,可以指定最多5个测试函数,每个函数都是单独运行在一个 + DUT 上的测试入口点。 + +在不同的 DUT 上运行的测试用例,通常会要求它们之间进行同步。我们提供 +``unity_wait_for_signal`` 和 ``unity_send_signal`` 这两个函数来使用 UART +去支持同步操作。如上例中的场景,slave 应该在在 master 设置好 GPIO +电平后再去读取 GPIO 电平,DUT 的 UART +终端会打印提示信息,并要求用户进行交互。 + +DUT1(master)终端: + +.. code:: bash + + Waiting for signal: [output high level]! + Please press "Enter" key once any board send this signal. + +DUT2(slave)终端: + +.. code:: bash + + Send signal: [output high level]! + +一旦 DUT2 发送了该信号,您需要在 DUT2 的终端输入回车,然后 DUT1 会从 +``unity_wait_for_signal`` 函数中解除阻塞,并开始更改 GPIO 的电平。 + + +添加多阶段测试用例 +------------------ + +常规的测试用例无需重启就会结束(或者仅需要检查是否发生了重启),可有些时候我们想在某些特定类型的重启事件后运行指定的测试代码,例如,我们想在深度睡眠唤醒后检查复位的原因是否正确。首先我们需要出发深度睡眠复位事件,然后检查复位的原因。为了实现这一点,我们可以定义多阶段测试用例来将这些测试函数组合在一起。 + +.. code:: c + + static void trigger_deepsleep(void) + { + esp_sleep_enable_timer_wakeup(2000); + esp_deep_sleep_start(); + } + + void check_deepsleep_reset_reason() + { + RESET_REASON reason = rtc_get_reset_reason(0); + TEST_ASSERT(reason == DEEPSLEEP_RESET); + } + + TEST_CASE_MULTIPLE_STAGES("reset reason check for deepsleep", "[esp32]", trigger_deepsleep, check_deepsleep_reset_reason); + +多阶段测试用例向用户呈现了一组测试函数,它需要用户进行交互(选择用例并选择不同的阶段)来运行。 + + +编译单元测试程序 +---------------- + +按照 esp-idf 顶层目录的 README 文件中的说明进行操作,请确保 ``IDF_PATH`` +环境变量已经被设置指向了 esp-idf 的顶层目录。 + +切换到 ``tools/unit-test-app`` 目录下进行配置和编译: + +- ``idf.py menuconfig`` - 配置单元测试程序。 + +- ``idf.py build -T all`` - 编译单元测试程序,测试每个组件 ``test`` + 子目录下的用例。 + +- ``idf.py build -T xxx`` - 编译单元测试程序,测试指定的组件。 + +- ``idf.py build -T all -E xxx`` - + 编译单元测试程序,测试所有(除开指定)的组件。例如 + ``idf.py build -T all -E ulp mbedtls`` - + 编译所有的单元测试,不包括 ``ulp`` 和 ``mbedtls``\ 组件。 + +当编译完成时,它会打印出烧写芯片的指令。您只需要运行 ``idf.py flash`` +即可烧写所有编译输出的文件。 + +您还可以运行 ``idf.py flash -T all`` 或者 +``idf.py flash -T xxx`` +来编译并烧写,所有需要的文件都会在烧写之前自动重新编译。 + +使用 ``menuconfig`` 可以设置烧写测试程序所使用的串口。 + + +运行单元测试 +------------ + +烧写完成后重启 ESP32, 它将启动单元测试程序。 + +当单元测试应用程序空闲时,输入回车键,它会打印出测试菜单,其中包含所有的测试项目。 + +.. code:: bash + + Here's the test menu, pick your combo: + (1) "esp_ota_begin() verifies arguments" [ota] + (2) "esp_ota_get_next_update_partition logic" [ota] + (3) "Verify bootloader image in flash" [bootloader_support] + (4) "Verify unit test app image" [bootloader_support] + (5) "can use new and delete" [cxx] + (6) "can call virtual functions" [cxx] + (7) "can use static initializers for non-POD types" [cxx] + (8) "can use std::vector" [cxx] + (9) "static initialization guards work as expected" [cxx] + (10) "global initializers run in the correct order" [cxx] + (11) "before scheduler has started, static initializers work correctly" [cxx] + (12) "adc2 work with wifi" [adc] + (13) "gpio master/slave test example" [ignore][misc][test_env=UT_T2_1][multi_device] + (1) "gpio_master_test" + (2) "gpio_slave_test" + (14) "SPI Master clockdiv calculation routines" [spi] + (15) "SPI Master test" [spi][ignore] + (16) "SPI Master test, interaction of multiple devs" [spi][ignore] + (17) "SPI Master no response when switch from host1 (HSPI) to host2 (VSPI)" [spi] + (18) "SPI Master DMA test, TX and RX in different regions" [spi] + (19) "SPI Master DMA test: length, start, not aligned" [spi] + (20) "reset reason check for deepsleep" [esp32][test_env=UT_T2_1][multi_stage] + (1) "trigger_deepsleep" + (2) "check_deepsleep_reset_reason" + +常规测试用例会打印用例名字和描述,主从测试用例还会打印子菜单(已注册的测试函数的名字)。 + +可以输入以下任意一项来运行测试用例: + +- 引号中的测试用例的名字,运行单个测试用例。 + +- 测试用例的序号,运行单个测试用例。 + +- 方括号中的模块名字,运行指定模块所有的测试用例。 + +- 星号,运行所有测试用例。 + +``[multi_device]`` 和 ``[multi_stage]`` +标签告诉测试运行者该用例是多设备测试还是多阶段测试。这些标签由 +``TEST_CASE_MULTIPLE_STAGES`` 和 ``TEST_CASE_MULTIPLE_DEVICES`` +宏自动生成。 + +一旦选择了多设备测试用例,它会打印一个子菜单: + +.. code:: bash + + Running gpio master/slave test example... + gpio master/slave test example + (1) "gpio_master_test" + (2) "gpio_slave_test" + +您需要输入数字以选择在 DUT 上运行的测试。 + +与多设备测试用例相似,多阶段测试用例也会打印子菜单: + +.. code:: bash + + Running reset reason check for deepsleep... + reset reason check for deepsleep + (1) "trigger_deepsleep" + (2) "check_deepsleep_reset_reason" + +第一次执行此用例时,输入 ``1`` 来运行第一阶段(触发深度睡眠)。在重启 +DUT 并再次选择运行此用例后,输入 ``2`` +来运行第二阶段。只有在最后一个阶段通过并且之前所有的阶段都成功触发了复位的情况下,该测试才算通过。 diff --git a/tools/ci/check_ut_cmake_make.sh b/tools/ci/check_ut_cmake_make.sh new file mode 100755 index 000000000..34d5783f8 --- /dev/null +++ b/tools/ci/check_ut_cmake_make.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# While we support GNU Make & CMake together, check that unit tests support both +CMAKE_UT_PATHS=$( find ${IDF_PATH}/components/ -type f -name CMakeLists.txt | grep "/test/" | grep -v "mbedtls/programs") +MAKE_UT_PATHS=$( find ${IDF_PATH}/components/ -type f -name component.mk | grep "/test/" ) + +CMAKE_UT_PATHS="$(/usr/bin/dirname $CMAKE_UT_PATHS | sort -n)" +MAKE_UT_PATHS="$(/usr/bin/dirname $MAKE_UT_PATHS | sort -n)" + +MISMATCH=$(comm -3 <(echo "$MAKE_UT_PATHS") <(echo "$CMAKE_UT_PATHS")) + +if [ -n "$MISMATCH" ]; then + echo "Some unit tests are not in both CMake and GNU Make:" + echo "$MISMATCH" + exit 1 +fi + +echo "Unit tests match" +exit 0 \ No newline at end of file diff --git a/tools/ci/executable-list.txt b/tools/ci/executable-list.txt index 89c03a120..729edc2cf 100644 --- a/tools/ci/executable-list.txt +++ b/tools/ci/executable-list.txt @@ -50,6 +50,7 @@ tools/windows/eclipse_make.sh tools/ci/build_examples_cmake.sh tools/ci/test_build_system_cmake.sh tools/ci/check_examples_cmake_make.sh +tools/ci/check_ut_cmake_make.sh tools/cmake/convert_to_cmake.py tools/cmake/run_cmake_lint.sh tools/idf.py diff --git a/tools/cmake/components.cmake b/tools/cmake/components.cmake index bb4be0c86..bafad5c42 100644 --- a/tools/cmake/components.cmake +++ b/tools/cmake/components.cmake @@ -16,7 +16,7 @@ endfunction() # function(register_component) get_filename_component(component_dir ${CMAKE_CURRENT_LIST_FILE} DIRECTORY) - get_filename_component(component ${component_dir} NAME) + set(component ${COMPONENT_NAME}) spaces2list(COMPONENT_SRCDIRS) spaces2list(COMPONENT_ADD_INCLUDEDIRS) @@ -102,6 +102,11 @@ function(register_component) endif() target_include_directories(${component} PRIVATE ${abs_dir}) endforeach() + + if(component IN_LIST BUILD_TEST_COMPONENTS) + target_link_libraries(${component} "-L${CMAKE_CURRENT_BINARY_DIR}") + target_link_libraries(${component} "-Wl,--whole-archive -l${component} -Wl,--no-whole-archive") + endif() endfunction() function(register_config_only_component) @@ -153,7 +158,7 @@ function(components_finish_registration) get_target_property(a_type ${a} TYPE) if(${a_type} MATCHES .+_LIBRARY) - set(COMPONENT_LIBRARIES "${COMPONENT_LIBRARIES};${a}") + list(APPEND COMPONENT_LIBRARIES ${a}) endif() endif() endforeach() diff --git a/tools/cmake/idf_functions.cmake b/tools/cmake/idf_functions.cmake index f84deb70a..fd5fce0c7 100644 --- a/tools/cmake/idf_functions.cmake +++ b/tools/cmake/idf_functions.cmake @@ -255,4 +255,4 @@ function(idf_get_git_revision) add_definitions(-DIDF_VER=\"${IDF_VER}\") git_submodule_check("${IDF_PATH}") set(IDF_VER ${IDF_VER} PARENT_SCOPE) -endfunction() +endfunction() \ No newline at end of file diff --git a/tools/cmake/project.cmake b/tools/cmake/project.cmake index 8784a9e9b..94fe0930d 100644 --- a/tools/cmake/project.cmake +++ b/tools/cmake/project.cmake @@ -63,6 +63,10 @@ macro(project name) execute_process(COMMAND "${CMAKE_COMMAND}" -D "COMPONENTS=${COMPONENTS}" -D "COMPONENT_REQUIRES_COMMON=${COMPONENT_REQUIRES_COMMON}" + -D "EXCLUDE_COMPONENTS=${EXCLUDE_COMPONENTS}" + -D "TEST_COMPONENTS=${TEST_COMPONENTS}" + -D "TEST_EXCLUDE_COMPONENTS=${TEST_EXCLUDE_COMPONENTS}" + -D "TESTS_ALL=${TESTS_ALL}" -D "DEPENDENCIES_FILE=${CMAKE_BINARY_DIR}/component_depends.cmake" -D "COMPONENT_DIRS=${COMPONENT_DIRS}" -D "BOOTLOADER_BUILD=${BOOTLOADER_BUILD}" @@ -83,6 +87,14 @@ macro(project name) unset(BUILD_COMPONENTS_SPACES) message(STATUS "Component paths: ${BUILD_COMPONENT_PATHS}") + # Print list of test components + if(TESTS_ALL EQUAL 1 OR TEST_COMPONENTS) + string(REPLACE ";" " " BUILD_TEST_COMPONENTS_SPACES "${BUILD_TEST_COMPONENTS}") + message(STATUS "Test component names: ${BUILD_TEST_COMPONENTS_SPACES}") + unset(BUILD_TEST_COMPONENTS_SPACES) + message(STATUS "Test component paths: ${BUILD_TEST_COMPONENT_PATHS}") + endif() + kconfig_set_variables() kconfig_process_config() @@ -124,7 +136,15 @@ macro(project name) # Add each component to the build as a library # foreach(COMPONENT_PATH ${BUILD_COMPONENT_PATHS}) - get_filename_component(COMPONENT_NAME ${COMPONENT_PATH} NAME) + list(FIND BUILD_TEST_COMPONENT_PATHS ${COMPONENT_PATH} idx) + + if(NOT idx EQUAL -1) + list(GET BUILD_TEST_COMPONENTS ${idx} test_component) + set(COMPONENT_NAME ${test_component}) + else() + get_filename_component(COMPONENT_NAME ${COMPONENT_PATH} NAME) + endif() + add_subdirectory(${COMPONENT_PATH} ${COMPONENT_NAME}) endforeach() unset(COMPONENT_NAME) diff --git a/tools/cmake/scripts/expand_requirements.cmake b/tools/cmake/scripts/expand_requirements.cmake index d82dfa296..56d78fd3b 100644 --- a/tools/cmake/scripts/expand_requirements.cmake +++ b/tools/cmake/scripts/expand_requirements.cmake @@ -82,14 +82,14 @@ endmacro() # return the path to the component in 'variable' # # Fatal error is printed if the component is not found. -function(find_component_path find_name component_paths variable) - foreach(path ${component_paths}) - get_filename_component(name "${path}" NAME) - if("${name}" STREQUAL "${find_name}") - set("${variable}" "${path}" PARENT_SCOPE) - return() - endif() - endforeach() +function(find_component_path find_name components component_paths variable) + list(FIND components ${find_name} idx) + if(NOT idx EQUAL -1) + list(GET component_paths ${idx} path) + set("${variable}" "${path}" PARENT_SCOPE) + return() + else() + endif() # TODO: find a way to print the dependency chain that lead to this not-found component message(WARNING "Required component ${find_name} is not found in any of the provided COMPONENT_DIRS") endfunction() @@ -100,10 +100,11 @@ endfunction() # # component_paths contains only unique component names. Directories # earlier in the component_dirs list take precedence. -function(components_find_all component_dirs component_paths component_names) +function(components_find_all component_dirs component_paths component_names test_component_names) # component_dirs entries can be files or lists of files set(paths "") set(names "") + set(test_names "") # start by expanding the component_dirs list with all subdirectories foreach(dir ${component_dirs}) @@ -123,10 +124,15 @@ function(components_find_all component_dirs component_paths component_names) get_filename_component(component "${component}" DIRECTORY) get_filename_component(name "${component}" NAME) if(NOT name IN_LIST names) - set(names "${names};${name}") - set(paths "${paths};${component}") - endif() + list(APPEND names "${name}") + list(APPEND paths "${component}") + # Look for test component directory + file(GLOB test "${component}/test/CMakeLists.txt") + if(test) + list(APPEND test_names "${name}") + endif() + endif() else() # no CMakeLists.txt file # test for legacy component.mk and warn file(GLOB legacy_component "${dir}/component.mk") @@ -136,25 +142,26 @@ function(components_find_all component_dirs component_paths component_names) "Component will be skipped.") endif() endif() - endforeach() set(${component_paths} ${paths} PARENT_SCOPE) set(${component_names} ${names} PARENT_SCOPE) + set(${test_component_names} ${test_names} PARENT_SCOPE) endfunction() + # expand_component_requirements: Recursively expand a component's requirements, # setting global properties BUILD_COMPONENTS & BUILD_COMPONENT_PATHS and # also invoking the components to call register_component() above, # which will add per-component global properties with dependencies, etc. function(expand_component_requirements component) get_property(seen_components GLOBAL PROPERTY SEEN_COMPONENTS) - if(${component} IN_LIST seen_components) + if(component IN_LIST seen_components) return() # already added, or in process of adding, this component endif() set_property(GLOBAL APPEND PROPERTY SEEN_COMPONENTS ${component}) - find_component_path("${component}" "${ALL_COMPONENT_PATHS}" component_path) + find_component_path("${component}" "${ALL_COMPONENTS}" "${ALL_COMPONENT_PATHS}" component_path) debug("Expanding dependencies of ${component} @ ${component_path}") if(NOT component_path) set_property(GLOBAL APPEND PROPERTY COMPONENTS_NOT_FOUND ${component}) @@ -176,27 +183,99 @@ function(expand_component_requirements component) expand_component_requirements(${req}) endforeach() + list(FIND TEST_COMPONENTS ${component} idx) + + if(NOT idx EQUAL -1) + list(GET TEST_COMPONENTS ${idx} test_component) + list(GET TEST_COMPONENT_PATHS ${idx} test_component_path) + set_property(GLOBAL APPEND PROPERTY BUILD_TEST_COMPONENTS ${test_component}) + set_property(GLOBAL APPEND PROPERTY BUILD_TEST_COMPONENT_PATHS ${test_component_path}) + endif() + # Now append this component to the full list (after its dependencies) set_property(GLOBAL APPEND PROPERTY BUILD_COMPONENT_PATHS ${component_path}) set_property(GLOBAL APPEND PROPERTY BUILD_COMPONENTS ${component}) endfunction() +# filter_components_list: Filter the components included in the build +# as specified by the user. Or, in the case of unit testing, filter out +# the test components to be built. +macro(filter_components_list) + spaces2list(COMPONENTS) + spaces2list(EXCLUDE_COMPONENTS) + spaces2list(TEST_COMPONENTS) + spaces2list(TEST_EXCLUDE_COMPONENTS) + + list(LENGTH ALL_COMPONENTS all_components_length) + math(EXPR all_components_length "${all_components_length} - 1") + + foreach(component_idx RANGE 0 ${all_components_length}) + list(GET ALL_COMPONENTS ${component_idx} component) + list(GET ALL_COMPONENT_PATHS ${component_idx} component_path) + + if(COMPONENTS) + if(${component} IN_LIST COMPONENTS) + set(add_component 1) + else() + set(add_component 0) + endif() + else() + set(add_component 1) + + endif() + + if(NOT ${component} IN_LIST EXCLUDE_COMPONENTS AND add_component EQUAL 1) + list(APPEND components ${component}) + list(APPEND component_paths ${component_path}) + + if(TESTS_ALL EQUAL 1 OR TEST_COMPONENTS) + if(NOT TESTS_ALL EQUAL 1 AND TEST_COMPONENTS) + if(${component} IN_LIST TEST_COMPONENTS) + set(add_test_component 1) + else() + set(add_test_component 0) + endif() + else() + set(add_test_component 1) + endif() + + if(${component} IN_LIST ALL_TEST_COMPONENTS) + if(NOT ${component} IN_LIST TEST_EXCLUDE_COMPONENTS AND add_test_component EQUAL 1) + list(APPEND test_components ${component}_test) + list(APPEND test_component_paths ${component_path}/test) + + list(APPEND components ${component}_test) + list(APPEND component_paths ${component_path}/test) + endif() + endif() + endif() + endif() + endforeach() + + set(COMPONENTS ${components}) + + set(TEST_COMPONENTS ${test_components}) + set(TEST_COMPONENT_PATHS ${test_component_paths}) + + list(APPEND ALL_COMPONENTS "${TEST_COMPONENTS}") + list(APPEND ALL_COMPONENT_PATHS "${TEST_COMPONENT_PATHS}") +endmacro() + # Main functionality goes here - # Find every available component in COMPONENT_DIRS, save as ALL_COMPONENT_PATHS and ALL_COMPONENTS -components_find_all("${COMPONENT_DIRS}" ALL_COMPONENT_PATHS ALL_COMPONENTS) +components_find_all("${COMPONENT_DIRS}" ALL_COMPONENT_PATHS ALL_COMPONENTS ALL_TEST_COMPONENTS) -if(NOT COMPONENTS) - set(COMPONENTS "${ALL_COMPONENTS}") -endif() -spaces2list(COMPONENTS) +filter_components_list() debug("ALL_COMPONENT_PATHS ${ALL_COMPONENT_PATHS}") debug("ALL_COMPONENTS ${ALL_COMPONENTS}") +debug("ALL_TEST_COMPONENTS ${ALL_TEST_COMPONENTS}") set_property(GLOBAL PROPERTY SEEN_COMPONENTS "") # anti-infinite-recursion set_property(GLOBAL PROPERTY BUILD_COMPONENTS "") set_property(GLOBAL PROPERTY BUILD_COMPONENT_PATHS "") +set_property(GLOBAL PROPERTY BUILD_TEST_COMPONENTS "") +set_property(GLOBAL PROPERTY BUILD_TEST_COMPONENT_PATHS "") set_property(GLOBAL PROPERTY COMPONENTS_NOT_FOUND "") # Indicate that the component CMakeLists.txt is being included in the early expansion phase of the build, @@ -210,6 +289,8 @@ unset(CMAKE_BUILD_EARLY_EXPANSION) get_property(build_components GLOBAL PROPERTY BUILD_COMPONENTS) get_property(build_component_paths GLOBAL PROPERTY BUILD_COMPONENT_PATHS) +get_property(build_test_components GLOBAL PROPERTY BUILD_TEST_COMPONENTS) +get_property(build_test_component_paths GLOBAL PROPERTY BUILD_TEST_COMPONENT_PATHS) get_property(not_found GLOBAL PROPERTY COMPONENTS_NOT_FOUND) debug("components in build: ${build_components}") @@ -223,6 +304,8 @@ endfunction() file(WRITE "${DEPENDENCIES_FILE}.tmp" "# Component requirements generated by expand_requirements.cmake\n\n") line("set(BUILD_COMPONENTS ${build_components})") line("set(BUILD_COMPONENT_PATHS ${build_component_paths})") +line("set(BUILD_TEST_COMPONENTS ${build_test_components})") +line("set(BUILD_TEST_COMPONENT_PATHS ${build_test_component_paths})") line("") line("# get_component_requirements: Generated function to read the dependencies of a given component.") diff --git a/tools/idf.py b/tools/idf.py index 1d2f128ec..a4e0573d8 100755 --- a/tools/idf.py +++ b/tools/idf.py @@ -84,7 +84,6 @@ def _run_tool(tool_name, args, cwd): except subprocess.CalledProcessError as e: raise FatalError("%s failed with exit code %d" % (tool_name, e.returncode)) - def check_environment(): """ Verify the environment contains the top-level tools we need to operate @@ -152,7 +151,7 @@ def _ensure_build_directory(args, always_run_cmake=False): # Verify/create the build directory build_dir = args.build_dir if not os.path.isdir(build_dir): - os.mkdir(build_dir) + os.makedirs(build_dir) cache_path = os.path.join(build_dir, "CMakeCache.txt") if not os.path.exists(cache_path) or always_run_cmake: if args.generator is None: @@ -163,7 +162,10 @@ def _ensure_build_directory(args, always_run_cmake=False): cmake_args += [ "--warn-uninitialized" ] if args.no_ccache: cmake_args += [ "-DCCACHE_DISABLE=1" ] + if args.define_cache_entry: + cmake_args += ["-D" + d for d in args.define_cache_entry] cmake_args += [ project_dir] + _run_tool("cmake", cmake_args, cwd=args.build_dir) except: # don't allow partially valid CMakeCache.txt files, @@ -220,6 +222,7 @@ def build_target(target_name, args): """ _ensure_build_directory(args) generator_cmd = GENERATOR_CMDS[args.generator] + if not args.no_ccache: # Setting CCACHE_BASEDIR & CCACHE_NO_HASHDIR ensures that project paths aren't stored in the ccache entries # (this means ccache hits can be shared between different projects. It may mean that some debug information @@ -258,13 +261,11 @@ def flash(action, args): esptool_args += [ "write_flash", "@"+flasher_args_path ] _run_tool("esptool.py", esptool_args, args.build_dir) - def erase_flash(action, args): esptool_args = _get_esptool_args(args) esptool_args += [ "erase_flash" ] _run_tool("esptool.py", esptool_args, args.build_dir) - def monitor(action, args): """ Run idf_monitor.py to watch build output @@ -331,7 +332,6 @@ def fullclean(action, args): def print_closing_message(args): # print a closing message of some kind # - if "flash" in str(args.actions): print("Done") return @@ -380,29 +380,29 @@ def print_closing_message(args): ACTIONS = { # action name : ( function (or alias), dependencies, order-only dependencies ) - "all" : ( build_target, [], [ "reconfigure", "menuconfig", "clean", "fullclean" ] ), - "build": ( "all", [], [] ), # build is same as 'all' target - "clean": ( clean, [], [ "fullclean" ] ), - "fullclean": ( fullclean, [], [] ), - "reconfigure": ( reconfigure, [], [ "menuconfig" ] ), - "menuconfig": ( build_target, [], [] ), - "confserver": ( build_target, [], [] ), - "size": ( build_target, [ "app" ], [] ), - "size-components": ( build_target, [ "app" ], [] ), - "size-files": ( build_target, [ "app" ], [] ), - "bootloader": ( build_target, [], [] ), - "bootloader-clean": ( build_target, [], [] ), - "bootloader-flash": ( flash, [ "bootloader" ], [ "erase_flash"] ), - "app": ( build_target, [], [ "clean", "fullclean", "reconfigure" ] ), - "app-flash": ( flash, [ "app" ], [ "erase_flash"]), - "partition_table": ( build_target, [], [ "reconfigure" ] ), - "partition_table-flash": ( flash, [ "partition_table" ], [ "erase_flash" ]), - "flash": ( flash, [ "all" ], [ "erase_flash" ] ), - "erase_flash": ( erase_flash, [], []), - "monitor": ( monitor, [], [ "flash", "partition_table-flash", "bootloader-flash", "app-flash" ]), + "all" : ( build_target, [], [ "reconfigure", "menuconfig", "clean", "fullclean" ] ), + "build": ( "all", [], [] ), # build is same as 'all' target + "clean": ( clean, [], [ "fullclean" ] ), + "fullclean": ( fullclean, [], [] ), + "reconfigure": ( reconfigure, [], [ "menuconfig" ] ), + "menuconfig": ( build_target, [], [] ), + "defconfig": ( build_target, [], [] ), + "confserver": ( build_target, [], [] ), + "size": ( build_target, [ "app" ], [] ), + "size-components": ( build_target, [ "app" ], [] ), + "size-files": ( build_target, [ "app" ], [] ), + "bootloader": ( build_target, [], [] ), + "bootloader-clean": ( build_target, [], [] ), + "bootloader-flash": ( flash, [ "bootloader" ], [ "erase_flash"] ), + "app": ( build_target, [], [ "clean", "fullclean", "reconfigure" ] ), + "app-flash": ( flash, [ "app" ], [ "erase_flash"]), + "partition_table": ( build_target, [], [ "reconfigure" ] ), + "partition_table-flash": ( flash, [ "partition_table" ], [ "erase_flash" ]), + "flash": ( flash, [ "all" ], [ "erase_flash" ] ), + "erase_flash": ( erase_flash, [], []), + "monitor": ( monitor, [], [ "flash", "partition_table-flash", "bootloader-flash", "app-flash" ]), } - def get_commandline_options(): """ Return all the command line options up to but not including the action """ result = [] @@ -427,6 +427,15 @@ def get_default_serial_port(): except IndexError: raise RuntimeError("No serial ports found. Connect a device, or use '-p PORT' option to set a specific port.") +# Import the actions, arguments extension file +if os.path.exists(os.path.join(os.getcwd(), "idf_ext.py")): + sys.path.append(os.getcwd()) + try: + from idf_ext import add_action_extensions, add_argument_extensions + except ImportError as e: + print("Error importing extension file idf_ext.py. Skipping.") + print("Please make sure that it contains implementations (even if they're empty implementations) of") + print("add_action_extensions and add_argument_extensions.") def main(): if sys.version_info[0] != 2 or sys.version_info[1] != 7: @@ -434,6 +443,19 @@ def main(): "you encounter. Search for 'Setting the Python Interpreter' in the ESP-IDF docs if you want to use " "Python 2.7." % sys.version_info[:3]) + # Add actions extensions + try: + add_action_extensions({ + "build_target": build_target, + "reconfigure" : reconfigure, + "flash" : flash, + "monitor" : monitor, + "clean" : clean, + "fullclean" : fullclean + }, ACTIONS) + except NameError: + pass + parser = argparse.ArgumentParser(description='ESP-IDF build management tool') parser.add_argument('-p', '--port', help="Serial port", default=os.environ.get('ESPPORT', None)) @@ -444,10 +466,17 @@ def main(): parser.add_argument('-G', '--generator', help="Cmake generator", choices=GENERATOR_CMDS.keys()) parser.add_argument('-n', '--no-warnings', help="Disable Cmake warnings", action="store_true") parser.add_argument('-v', '--verbose', help="Verbose build output", action="store_true") + parser.add_argument('-D', '--define-cache-entry', help="Create a cmake cache entry", nargs='+') parser.add_argument('--no-ccache', help="Disable ccache. Otherwise, if ccache is available on the PATH then it will be used for faster builds.", action="store_true") parser.add_argument('actions', help="Actions (build targets or other operations)", nargs='+', choices=ACTIONS.keys()) + # Add arguments extensions + try: + add_argument_extensions(parser) + except NameError: + pass + args = parser.parse_args() check_environment() diff --git a/tools/unit-test-app/CMakeLists.txt b/tools/unit-test-app/CMakeLists.txt new file mode 100644 index 000000000..95932b0c2 --- /dev/null +++ b/tools/unit-test-app/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(unit-test-app) \ No newline at end of file diff --git a/tools/unit-test-app/README.md b/tools/unit-test-app/README.md index 358992f3e..9e197dfda 100644 --- a/tools/unit-test-app/README.md +++ b/tools/unit-test-app/README.md @@ -4,6 +4,8 @@ ESP-IDF unit tests are run using Unit Test App. The app can be built with the un # Building Unit Test App +## GNU Make + * Follow the setup instructions in the top-level esp-idf README. * Set IDF_PATH environment variable to point to the path to the esp-idf top-level directory. * Change into `tools/unit-test-app` directory @@ -12,11 +14,21 @@ ESP-IDF unit tests are run using Unit Test App. The app can be built with the un * Follow the printed instructions to flash, or run `make flash`. * Unit test have a few preset sdkconfigs. It provides command `make ut-clean-config_name` and `make ut-build-config_name` (where `config_name` is the file name under `unit-test-app/configs` folder) to build with preset configs. For example, you can use `make ut-build-default TESTS_ALL=1` to build with config file `unit-test-app/configs/default`. Built binary for this config will be copied to `unit-test-app/output/config_name` folder. +## CMake + +* Follow the setup instructions in the top-level esp-idf README. +* Set IDF_PATH environment variable to point to the path to the esp-idf top-level directory. +* Change into `tools/unit-test-app` directory +* `idf.py menuconfig` to configure the Unit Test App. +* `idf.py build -T ...` with `component` set to names of the components to be included in the test app. Or `idf.py build -T all` to build the test app with all the tests for components having `test` subdirectory. +* Follow the printed instructions to flash, or run `idf.py flash -p PORT`. +* Unit test have a few preset sdkconfigs. It provides command `idf.py ut-clean-config_name` and `idf.py ut-build-config_name` (where `config_name` is the file name under `unit-test-app/configs` folder) to build with preset configs. For example, you can use `idf.py ut-build-default -T all` to build with config file `unit-test-app/configs/default`. Built binary for this config will be copied to `unit-test-app/output/config_name` folder. + # Flash Size -The unit test partition table assumes a 4MB flash size. When testing `TESTS_ALL=1`, this additional factory app partition size is required. +The unit test partition table assumes a 4MB flash size. When testing `TESTS_ALL=1` (Make) or `-T all` (CMake), this additional factory app partition size is required. -If building unit tests to run on a smaller flash size, edit `partition_table_unit_tests_app.csv` and use `TEST_COMPONENTS=` instead of `TESTS_ALL` if tests don't fit in a smaller factory app partition (exact size will depend on configured options). +If building unit tests to run on a smaller flash size, edit `partition_table_unit_tests_app.csv` and use `TEST_COMPONENTS=` (Make) or `-T ...` (CMake) instead of `TESTS_ALL` or `-T all` if tests don't fit in a smaller factory app partition (exact size will depend on configured options). # Running Unit Tests diff --git a/tools/unit-test-app/components/unity/CMakeLists.txt b/tools/unit-test-app/components/unity/CMakeLists.txt new file mode 100644 index 000000000..98d8fa6d2 --- /dev/null +++ b/tools/unit-test-app/components/unity/CMakeLists.txt @@ -0,0 +1,10 @@ +set(COMPONENT_SRCDIRS .) +set(COMPONENT_ADD_INCLUDEDIRS include) + +set(COMPONENT_REQUIRES spi_flash idf_test) + +register_component() + +if(GCC_NOT_5_2_0) + component_compile_options(-Wno-unused-const-variable) +endif() \ No newline at end of file diff --git a/tools/unit-test-app/idf_ext.py b/tools/unit-test-app/idf_ext.py new file mode 100644 index 000000000..13a01a311 --- /dev/null +++ b/tools/unit-test-app/idf_ext.py @@ -0,0 +1,279 @@ +import sys +import glob +import tempfile +import os +import os.path +import re +import shutil +import argparse +import json +import copy + +PROJECT_NAME = "unit-test-app" +PROJECT_PATH = os.getcwd() + +# List of unit-test-app configurations. +# Each file in configs/ directory defines a configuration. The format is the +# same as sdkconfig file. Configuration is applied on top of sdkconfig.defaults +# file from the project directory +CONFIG_NAMES = os.listdir(os.path.join(PROJECT_PATH, "configs")) + +# Build (intermediate) and output (artifact) directories +BUILDS_DIR = os.path.join(PROJECT_PATH, "builds") +BINARIES_DIR = os.path.join(PROJECT_PATH, "output") + +# Convert the values passed to the -T parameter to corresponding cache entry definitions +# TESTS_ALL and TEST_COMPONENTS +class TestComponentAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + # Create a new of cache definition entry, adding previous elements + cache_entries = list() + + existing_entries = getattr(namespace, "define_cache_entry", []) + + if existing_entries: + cache_entries.extend(existing_entries) + + # Form -D arguments + if "all" in values: + cache_entries.append("TESTS_ALL=1") + cache_entries.append("TEST_COMPONENTS=''") + else: + cache_entries.append("TESTS_ALL=0") + cache_entries.append("TEST_COMPONENTS='%s'" % " ".join(values)) + + setattr(namespace, "define_cache_entry", cache_entries) + + # Brute force add reconfigure at the very beginning + existing_actions = getattr(namespace, "actions", []) + if not "reconfigure" in existing_actions: + existing_actions = ["reconfigure"] + existing_actions + setattr(namespace, "actions", existing_actions) + +class TestExcludeComponentAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + # Create a new of cache definition entry, adding previous elements + cache_entries = list() + + existing_entries = getattr(namespace, "define_cache_entry", []) + + if existing_entries: + cache_entries.extend(existing_entries) + + cache_entries.append("TEST_EXCLUDE_COMPONENTS='%s'" % " ".join(values)) + + setattr(namespace, "define_cache_entry", cache_entries) + + # Brute force add reconfigure at the very beginning + existing_actions = getattr(namespace, "actions", []) + if not "reconfigure" in existing_actions: + existing_actions = ["reconfigure"] + existing_actions + setattr(namespace, "actions", existing_actions) + +def add_argument_extensions(parser): + # For convenience, define a -T argument that gets converted to -D arguments + parser.add_argument('-T', '--test-component', help="Specify the components to test", nargs='+', action=TestComponentAction) + # For convenience, define a -T argument that gets converted to -D arguments + parser.add_argument('-E', '--test-exclude-components', help="Specify the components to exclude from testing", nargs='+', action=TestExcludeComponentAction) + +def add_action_extensions(base_functions, base_actions): + + def ut_apply_config(ut_apply_config_name, args): + config_name = re.match(r"ut-apply-config-(.*)", ut_apply_config_name).group(1) + + def set_config_build_variables(prop, defval = None): + property_value = re.match(r"^%s=(.*)" % prop, config_file_content) + if (property_value): + property_value = property_value.group(1) + else: + property_value = defval + + if (property_value): + try: + args.define_cache_entry.append("%s=" % prop + property_value) + except AttributeError: + args.define_cache_entry = ["%s=" % prop + property_value] + + return property_value + + sdkconfig_set = None + + if args.define_cache_entry: + sdkconfig_set = filter(lambda s: "SDKCONFIG=" in s, args.define_cache_entry) + + sdkconfig_path = os.path.join(args.project_dir, "sdkconfig") + + if sdkconfig_set: + sdkconfig_path = sdkconfig_set[-1].split("=")[1] + sdkconfig_path = os.path.abspath(sdkconfig_path) + + try: + os.remove(sdkconfig_path) + except OSError: + pass + + if config_name in CONFIG_NAMES: + # Parse the sdkconfig for components to be included/excluded and tests to be run + config = os.path.join(PROJECT_PATH, "configs", config_name) + + with open(config, "r") as config_file: + config_file_content = config_file.read() + + set_config_build_variables("EXCLUDE_COMPONENTS", "''") + + test_components = set_config_build_variables("TEST_COMPONENTS", "''") + + tests_all = None + if test_components == "''": + tests_all = "TESTS_ALL=1" + else: + tests_all = "TESTS_ALL=0" + + try: + args.define_cache_entry.append(tests_all) + except AttributeError: + args.define_cache_entry = [tests_all] + + set_config_build_variables("TEST_EXCLUDE_COMPONENTS","''") + + with tempfile.NamedTemporaryFile() as sdkconfig_temp: + # Use values from the combined defaults and the values from + # config folder to build config + sdkconfig_default = os.path.join(PROJECT_PATH, "sdkconfig.defaults") + + with open(sdkconfig_default, "r") as sdkconfig_default_file: + sdkconfig_temp.write(sdkconfig_default_file.read()) + + sdkconfig_config = os.path.join(PROJECT_PATH, "configs", config_name) + with open(sdkconfig_config, "r") as sdkconfig_config_file: + sdkconfig_temp.write("\n") + sdkconfig_temp.write(sdkconfig_config_file.read()) + + sdkconfig_temp.flush() + + try: + args.define_cache_entry.append("SDKCONFIG_DEFAULTS=" + sdkconfig_temp.name) + except AttributeError: + args.define_cache_entry = ["SDKCONFIG_DEFAULTS=" + sdkconfig_temp.name] + + reconfigure = base_functions["reconfigure"] + reconfigure(None, args) + else: + if not config_name == "all-configs": + print("unknown unit test app config for action '%s'" % ut_apply_config_name) + + # This target builds the configuration. It does not currently track dependencies, + # but is good enough for CI builds if used together with clean-all-configs. + # For local builds, use 'apply-config-NAME' target and then use normal 'all' + # and 'flash' targets. + def ut_build(ut_build_name, args): + # Create a copy of the passed arguments to prevent arg modifications to accrue if + # all configs are being built + build_args = copy.copy(args) + + config_name = re.match(r"ut-build-(.*)", ut_build_name).group(1) + + if config_name in CONFIG_NAMES: + build_args.build_dir = os.path.join(BUILDS_DIR, config_name) + + src = os.path.join(BUILDS_DIR, config_name) + dest = os.path.join(BINARIES_DIR, config_name) + + try: + os.makedirs(dest) + except OSError: + pass + + # Build, tweaking paths to sdkconfig and sdkconfig.defaults + ut_apply_config("ut-apply-config-" + config_name, build_args) + + build_target = base_functions["build_target"] + + build_target("all", build_args) + + # Copy artifacts to the output directory + shutil.copyfile(os.path.join(build_args.project_dir, "sdkconfig"), os.path.join(dest, "sdkconfig")) + + binaries = [PROJECT_NAME + x for x in [".elf", ".bin", ".map"]] + + for binary in binaries: + shutil.copyfile(os.path.join(src, binary), os.path.join(dest, binary)) + + try: + os.mkdir(os.path.join(dest, "bootloader")) + except OSError: + pass + + shutil.copyfile(os.path.join(src, "bootloader", "bootloader.bin"), os.path.join(dest, "bootloader", "bootloader.bin")) + + for partition_table in glob.glob(os.path.join(src, "partition_table", "partition-table*.bin")): + try: + os.mkdir(os.path.join(dest, "partition_table")) + except OSError: + pass + shutil.copyfile(partition_table, os.path.join(dest, "partition_table", os.path.basename(partition_table))) + + shutil.copyfile(os.path.join(src, "flash_project_args"), os.path.join(dest, "flash_project_args")) + + binaries = glob.glob(os.path.join(src, "*.bin")) + binaries = [os.path.basename(s) for s in binaries] + + for binary in binaries: + shutil.copyfile(os.path.join(src, binary), os.path.join(dest, binary)) + + else: + if not config_name == "all-configs": + print("unknown unit test app config for action '%s'" % ut_build_name) + + def ut_clean(ut_clean_name, args): + config_name = re.match(r"ut-clean-(.*)", ut_clean_name).group(1) + if config_name in CONFIG_NAMES: + shutil.rmtree(os.path.join(BUILDS_DIR, config_name), ignore_errors=True) + shutil.rmtree(os.path.join(BINARIES_DIR, config_name), ignore_errors=True) + else: + if not config_name == "all-configs": + print("unknown unit test app config for action '%s'" % ut_clean_name) + + def ut_help(action, args): + HELP_STRING = """ +Additional unit-test-app specific targets + +idf.py ut-build-NAME - Build unit-test-app with configuration provided in configs/NAME. + Build directory will be builds/NAME/, output binaries will be + under output/NAME/ + +idf.py ut-clean-NAME - Remove build and output directories for configuration NAME. + +idf.py ut-build-all-configs - Build all configurations defined in configs/ directory. + +idf.py ut-apply-config-NAME - Generates configuration based on configs/NAME in sdkconfig + file. After this, normal all/flash targets can be used. + Useful for development/debugging. +""" + print(HELP_STRING) + + # Build dictionary of action extensions + extensions = dict() + + # This generates per-config targets (clean, build, apply-config). + build_all_config_deps = [] + clean_all_config_deps = [] + + for config in CONFIG_NAMES: + config_build_action_name = "ut-build-" + config + config_clean_action_name = "ut-clean-" + config + config_apply_config_action_name = "ut-apply-config-" + config + + extensions[config_build_action_name] = (ut_build, [], []) + extensions[config_clean_action_name] = (ut_clean, [], []) + extensions[config_apply_config_action_name] = (ut_apply_config, [], []) + + build_all_config_deps.append(config_build_action_name) + clean_all_config_deps.append(config_clean_action_name) + + extensions["ut-build-all-configs"] = (ut_build, build_all_config_deps, []) + extensions["ut-clean-all-configs"] = (ut_clean, clean_all_config_deps, []) + + extensions["ut-help"] = (ut_help, [], []) + + base_actions.update(extensions) \ No newline at end of file diff --git a/tools/unit-test-app/main/CMakeLists.txt b/tools/unit-test-app/main/CMakeLists.txt new file mode 100644 index 000000000..47f681d36 --- /dev/null +++ b/tools/unit-test-app/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "app_main.c") +set(COMPONENT_ADD_INCLUDEDIRS "") + +register_component()