diff --git a/components/unity/Kconfig b/components/unity/Kconfig index 72952c1e53..19d11a3e96 100644 --- a/components/unity/Kconfig +++ b/components/unity/Kconfig @@ -58,4 +58,18 @@ menu "Unity unit testing library" jumping back to the test menu. The jumping is usually occurs in assert functions such as TEST_ASSERT, TEST_FAIL etc. + config UNITY_TEST_ORDER_BY_FILE_PATH_AND_LINE + bool "Order unit tests by file path and line number" + default n + help + If enabled, the Unity test framework will automatically insert test cases + in a sorted order at registration time (during constructor execution), + based on their source file path and line number. + + This ensures consistent execution order across platforms (e.g., Linux vs. on-chip), + preserving the logical order in which tests are written in the source files. + + Note, the file path used for sorting follows the full absolute path format. + /IDF/examples/system/unit_test/components/testable/test/test_mean.c + endmenu # "Unity unit testing library" diff --git a/components/unity/test_apps/.build-test-rules.yml b/components/unity/test_apps/.build-test-rules.yml new file mode 100644 index 0000000000..f802ab3653 --- /dev/null +++ b/components/unity/test_apps/.build-test-rules.yml @@ -0,0 +1,8 @@ +# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps + +components/unity/test_apps: + enable: + - if: IDF_TARGET in["esp32", "linux"] + reason: need to test on a chip and linux targets + depends_components: + - unity diff --git a/components/unity/test_apps/CMakeLists.txt b/components/unity/test_apps/CMakeLists.txt new file mode 100644 index 0000000000..72f70aab08 --- /dev/null +++ b/components/unity/test_apps/CMakeLists.txt @@ -0,0 +1,7 @@ +#This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(unity_test_app) diff --git a/components/unity/test_apps/README.md b/components/unity/test_apps/README.md new file mode 100644 index 0000000000..d01777b5e0 --- /dev/null +++ b/components/unity/test_apps/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | Linux | +| ----------------- | ----- | ----- | diff --git a/components/unity/test_apps/main/CMakeLists.txt b/components/unity/test_apps/main/CMakeLists.txt new file mode 100644 index 0000000000..c34ca00aa1 --- /dev/null +++ b/components/unity/test_apps/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRC_DIRS "." + PRIV_REQUIRES unity + WHOLE_ARCHIVE) diff --git a/components/unity/test_apps/main/test.c b/components/unity/test_apps/main/test.c new file mode 100644 index 0000000000..d0e66513b2 --- /dev/null +++ b/components/unity/test_apps/main/test.c @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "unity.h" + +TEST_CASE("Test 1", "[test]") +{ + TEST_ASSERT(1 == 1); +} + +TEST_CASE("Test 2", "[test]") +{ + TEST_ASSERT(1 == 1); +} + +TEST_CASE("Test 3", "[test]") +{ + TEST_ASSERT(1 == 1); +} + +TEST_CASE("Test 4", "[test]") +{ + TEST_ASSERT(1 == 1); +} diff --git a/components/unity/test_apps/main/test_app_main.c b/components/unity/test_apps/main/test_app_main.c new file mode 100644 index 0000000000..0302cd53d8 --- /dev/null +++ b/components/unity/test_apps/main/test_app_main.c @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "unity.h" + +void app_main(void) +{ + unity_run_menu(); +} diff --git a/components/unity/test_apps/pytest_unit_test.py b/components/unity/test_apps/pytest_unit_test.py new file mode 100644 index 0000000000..bb592778a1 --- /dev/null +++ b/components/unity/test_apps/pytest_unit_test.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +def verify_test_order(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests.') + dut.write('\n') + dut.expect('Test 1') + dut.expect('Test 2') + dut.expect('Test 3') + dut.expect('Test 4') + + +@pytest.mark.generic +@idf_parametrize('target', ['esp32'], indirect=['target']) +def test_unit_test_order(dut: Dut) -> None: + verify_test_order(dut) + + +@pytest.mark.host_test +@idf_parametrize('target', ['linux'], indirect=['target']) +def test_unit_test_order_linux(dut: Dut) -> None: + verify_test_order(dut) diff --git a/components/unity/test_apps/sdkconfig.defaults b/components/unity/test_apps/sdkconfig.defaults new file mode 100644 index 0000000000..43fdc0f587 --- /dev/null +++ b/components/unity/test_apps/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_ESP_TASK_WDT_EN=n +CONFIG_UNITY_TEST_ORDER_BY_FILE_PATH_AND_LINE=y diff --git a/components/unity/unity_runner.c b/components/unity/unity_runner.c index 3362f66568..eaea5710e5 100644 --- a/components/unity/unity_runner.c +++ b/components/unity/unity_runner.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -11,6 +11,7 @@ #include #include "unity.h" #include "esp_system.h" +#include "sdkconfig.h" /* similar to UNITY_PRINT_EOL */ #define UNITY_PRINT_TAB() UNITY_OUTPUT_CHAR('\t') @@ -24,11 +25,37 @@ void unity_testcase_register(test_desc_t *desc) if (!s_unity_tests_first) { s_unity_tests_first = desc; s_unity_tests_last = desc; - } else { - test_desc_t *temp = s_unity_tests_first; - s_unity_tests_first = desc; - s_unity_tests_first->next = temp; + return; } +#if CONFIG_UNITY_TEST_ORDER_BY_FILE_PATH_AND_LINE + test_desc_t *prev = NULL; + test_desc_t *current = s_unity_tests_first; + + while (current) { + int file_cmp = strcmp(desc->file, current->file); + if (file_cmp < 0 || (file_cmp == 0 && desc->line < current->line)) { + // Insert before current + if (prev) { + prev->next = desc; + } else { + // Inserting at the head + s_unity_tests_first = desc; + } + desc->next = current; + return; + } + prev = current; + current = current->next; + } + + // Insert at the end + prev->next = desc; + s_unity_tests_last = desc; +#else + // Insert at head (original behavior) + desc->next = s_unity_tests_first; + s_unity_tests_first = desc; +#endif } /* print the multiple function case name and its sub-menu