From 9a5981d9ddc6a20d9c21454416a3f22b014c35cc Mon Sep 17 00:00:00 2001 From: sexenteen <130895816+sexenteen@users.noreply.github.com> Date: Tue, 3 Nov 2026 23:31:45 +0800 Subject: [PATCH] feat[utest]: repeat the automatic run and sample free/ps between loops --- .github/workflows/utest_auto_run.yml | 31 +++++++++++++--- components/utilities/Kconfig | 10 ++++++ components/utilities/utest/utest.c | 53 ++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/.github/workflows/utest_auto_run.yml b/.github/workflows/utest_auto_run.yml index e05a26f1a963..6c3df7e562d5 100644 --- a/.github/workflows/utest_auto_run.yml +++ b/.github/workflows/utest_auto_run.yml @@ -51,6 +51,11 @@ on: required: false default: 'manual' type: string + utest_loop: + description: '自动运行重复次数 (1-100),大于 1 时每轮结束后执行 free/ps 观测内存与线程变化' + required: false + default: '1' + type: string concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -294,6 +299,16 @@ jobs: cat $UTEST_CONFIG_ROOT/$TEST_CONFIG_FILE >> $TEST_BSP_ROOT/.config echo >> $TEST_BSP_ROOT/.config + # Repeat the automatic utest run when a loop count is requested, so that + # resources leaked by a test case show up as growing memory/threads. + UTEST_LOOP="${{ github.event.inputs.utest_loop }}" + UTEST_LOOP="${UTEST_LOOP:-1}" + echo "UTEST_LOOP=$UTEST_LOOP" >> $GITHUB_ENV + if [[ "$UTEST_LOOP" != "1" ]]; then + echo "CONFIG_RT_UTEST_AUTO_RUN_LOOP=$UTEST_LOOP" >> $TEST_BSP_ROOT/.config + echo "utest auto run loop: $UTEST_LOOP" + fi + # Keep standard qemu tests free of PM/tickless side effects. if [[ "${{ matrix.platform.KERNEL }}" == "standard" && ( "$TEST_BSP_ROOT" == "bsp/qemu-virt64-riscv" || "$TEST_BSP_ROOT" == "bsp/qemu-virt64-aarch64" ) ]]; then echo "# CONFIG_RT_USING_PM is not set" >> $TEST_BSP_ROOT/.config @@ -413,7 +428,7 @@ jobs: if: ${{ env.TOOLCHAIN_INSTALLED != '' && success() }} # Same known-flaky qemu-virt64-riscv standard kernel utest run as the previous step. continue-on-error: ${{ matrix.platform.UTEST == 'RISCV' && (matrix.config_file == 'kernel/ipc.cfg' || matrix.config_file == 'kernel/kernel_basic.cfg') }} - timeout-minutes: 20 + timeout-minutes: 360 run: | LOG_FILE="qemu_output_${TEST_QEMU_ARCH}.log" FAILURE_DETECTED=false @@ -431,7 +446,15 @@ jobs: echo " VV VV " echo "==========================================================================================" set +e - timeout 20m bash -c ' + # Bound the QEMU wait by the loop count (20 minutes per loop). The step level + # timeout-minutes cannot take an expression, so the real limit is applied here. + LOOP_NUM="${UTEST_LOOP:-1}" + case "$LOOP_NUM" in + ''|*[!0-9]*) LOOP_NUM=1 ;; + esac + MONITOR_TIMEOUT_MIN=$(( LOOP_NUM * 20 )) + echo "Monitoring the QEMU log for at most ${MONITOR_TIMEOUT_MIN} minutes (utest_loop=${LOOP_NUM})" + timeout "${MONITOR_TIMEOUT_MIN}m" bash -c ' tail -n +1 -f "$1" | while IFS= read -r line; do echo "$line" if [[ "$line" == *"[ FAILED ] [ result ]"* ]]; then @@ -448,7 +471,7 @@ jobs: exit 1 fi - if [[ "$line" == *"[==========] [ utest ] finished"* ]]; then + if [[ "$line" == *"[==========] [ utest ] all loops finished"* ]]; then if [[ "$FAILURE_DETECTED" == "true" ]]; then echo "==========================================================================================" echo " || || " @@ -471,7 +494,7 @@ jobs: if [[ $monitor_status -eq 124 ]]; then echo "==========================================================================================" echo " || || " - echo " || Error: Utest run timed out after 20 minutes. Exiting... || " + echo " || Error: Utest run timed out after ${MONITOR_TIMEOUT_MIN} minutes. Exiting... || " echo " VV VV " echo "==========================================================================================" pkill -f "qemu-system-${TEST_QEMU_ARCH}.*${TEST_BSP_ROOT}/rtthread.bin" || true diff --git a/components/utilities/Kconfig b/components/utilities/Kconfig index c32cd692bde6..6958788734bb 100644 --- a/components/utilities/Kconfig +++ b/components/utilities/Kconfig @@ -226,6 +226,16 @@ config RT_USING_UTEST help If enable this option, the test cases will be run automatically when board boot up. + config RT_UTEST_AUTO_RUN_LOOP + int "Loop count of the automatic run" + depends on RT_UTEST_USING_AUTO_RUN + range 1 100 + default 1 + help + How many times the automatic run repeats the registered test cases. + When it is greater than 1, "free" and "ps" are executed after each + loop, so memory or thread leaks show up in the log as growing numbers. + if FINSH_USING_OPTION_COMPLETION config RT_UTEST_MAX_OPTIONS int "Maximum number of utest cases for auto-completion" diff --git a/components/utilities/utest/utest.c b/components/utilities/utest/utest.c index f9f80fdff923..95f73534f0cc 100644 --- a/components/utilities/utest/utest.c +++ b/components/utilities/utest/utest.c @@ -222,6 +222,39 @@ static int utest_help(void) return 0; } +#ifdef RT_UTEST_AUTO_RUN_LOOP +static void utest_loop_runtime_info(rt_uint32_t loop) +{ + if (tc_loop <= 1) + { + return; + } + + LOG_I("[==========] [ utest ] loop %d/%d runtime info", loop, tc_loop); + +#ifdef RT_USING_FINSH + { + extern int msh_exec(char *cmd, rt_size_t length); + static char free_cmd[] = "free"; + static char ps_cmd[] = "ps"; + + msh_exec(free_cmd, sizeof(free_cmd) - 1); + msh_exec(ps_cmd, sizeof(ps_cmd) - 1); + } +#else + { + rt_size_t total = 0; + rt_size_t used = 0; + rt_size_t max_used = 0; + + rt_memory_info(&total, &used, &max_used); + LOG_I("[==========] [ utest ] memory total:%d used:%d maximum:%d", + total, used, max_used); + } +#endif /* RT_USING_FINSH */ +} +#endif /* RT_UTEST_AUTO_RUN_LOOP */ + static void utest_do_run(const char *utest_name) { rt_size_t i; @@ -352,7 +385,18 @@ static void utest_do_run(const char *utest_name) } LOG_I("[==========] [ utest ] finished"); + +#ifdef RT_UTEST_AUTO_RUN_LOOP + utest_loop_runtime_info(index + 1); +#endif } + +#ifdef RT_UTEST_AUTO_RUN_LOOP + /* Emitted once, after every loop and its runtime info. CI uses this line + * instead of the per-loop "finished" one, otherwise it would stop after the + * first loop and miss the remaining loops and their free/ps output. */ + LOG_I("[==========] [ utest ] all loops finished"); +#endif } static void utest_thr_entry(void *para) @@ -378,7 +422,16 @@ static void utest_thread_create(const char *utest_name) #ifdef RT_UTEST_USING_AUTO_RUN static int utest_auto_run(void) { +#ifdef RT_UTEST_AUTO_RUN_LOOP + tc_loop = RT_UTEST_AUTO_RUN_LOOP; +#else tc_loop = 1; +#endif + if (tc_loop == 0) + { + tc_loop = 1; + } + utest_thread_create(RT_NULL); return RT_EOK; }