mirror of
https://github.com/eclipse-threadx/threadx.git
synced 2026-10-06 06:59:08 +08:00
Completed Windows simulator support and regression coverage (#736)
Completed and stabilized the Win32 and Win64 MSVC simulator ports. - Replaced high-latency host synchronization with bounded scheduler handoffs and critical sections. - Added a high-resolution timer, tick batching, idle fast-forward, shutdown coordination and Win64 extension-pointer support. - Brought the Windows regression tooling and the new thread-transition tests up on CMake, Ninja and the Visual Studio Build Tools. - Extended the SMP teardown diagnostics and corrected 64-bit trace-test handling. This supersedes the historical `win64`, `win32-perf`, `windows-sim-ports` and `windows-sim-ports-completion` branches; no unmerged change from them is missing here. The original Win64 port landed in #529. 1,610 of 1,610 tests pass, across five configurations each: Win32 515, Win64 515, Win64 SMP 580. No external dependency was added, and the existing MSVC warnings in the trace configuration are unchanged. Hardware validation does not apply to host simulator ports. Assisted-by: Codex (gpt-5.6-sol) <codex@openai.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
/* This is the test control routine of the ThreadX kernel. All tests are dispatched from this routine. */
|
||||
// Some portions generated by Codex (gpt 5.5).
|
||||
// Some portions generated by Copilot (Opus 5).
|
||||
// Some portions generated by Codex (gpt-5.6-sol).
|
||||
|
||||
#define TX_THREAD_SMP_SOURCE_CODE
|
||||
|
||||
@@ -28,11 +29,17 @@
|
||||
#include "tx_event_flags.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <process.h>
|
||||
#include <stdint.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
|
||||
#define TEST_STACK_SIZE 6144
|
||||
@@ -1377,9 +1384,10 @@ TX_THREAD *thread_ptr;
|
||||
that takes milliseconds, so bounding it cannot turn a slow pass into a
|
||||
failure.
|
||||
|
||||
The report is written with write() rather than printf() deliberately. A
|
||||
wedged thread may hold the stdio lock, and a watchdog that blocked on that
|
||||
lock would reproduce the silent timeout it exists to replace.
|
||||
The report is written directly to the standard-error descriptor rather
|
||||
than with printf() deliberately. A wedged thread may hold the stdio lock,
|
||||
and a watchdog that blocked on that lock would reproduce the silent timeout
|
||||
it exists to replace.
|
||||
|
||||
TX_TEST_TEARDOWN_TIMEOUT overrides the bound, in seconds; zero disables the
|
||||
watchdog. TX_TEST_TEARDOWN_TRACE echoes every stage as it is reached and
|
||||
@@ -1395,6 +1403,26 @@ static volatile UINT test_teardown_armed = TX_FALSE;
|
||||
static UINT test_teardown_timeout = TEST_TEARDOWN_TIMEOUT_DEFAULT;
|
||||
static UINT test_teardown_trace = TX_FALSE;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define TEST_TEARDOWN_DEFERRED_PREEMPT(thread_ptr) ((thread_ptr) -> tx_thread_win32_deferred_preempt)
|
||||
#define TEST_TEARDOWN_SUSPENSION_TYPE(thread_ptr) ((thread_ptr) -> tx_thread_win32_suspension_type)
|
||||
#else
|
||||
#define TEST_TEARDOWN_DEFERRED_PREEMPT(thread_ptr) ((thread_ptr) -> tx_thread_linux_deferred_preempt)
|
||||
#define TEST_TEARDOWN_SUSPENSION_TYPE(thread_ptr) ((thread_ptr) -> tx_thread_linux_suspension_type)
|
||||
#endif
|
||||
|
||||
|
||||
/* Write diagnostics directly to standard error without taking the stdio lock. */
|
||||
static void test_teardown_write(const char *buffer, size_t length)
|
||||
{
|
||||
|
||||
#ifdef _WIN32
|
||||
(void) _write(2, buffer, (unsigned int) length);
|
||||
#else
|
||||
(void) write(2, buffer, length);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/* Record how far teardown has progressed, and echo it when tracing is on. */
|
||||
static void test_teardown_stage(const char *stage)
|
||||
@@ -1404,9 +1432,9 @@ static void test_teardown_stage(const char *stage)
|
||||
|
||||
if (test_teardown_trace != TX_FALSE)
|
||||
{
|
||||
(void) write(2, "[teardown] ", 11);
|
||||
(void) write(2, stage, strlen(stage));
|
||||
(void) write(2, "\n", 1);
|
||||
test_teardown_write("[teardown] ", 11u);
|
||||
test_teardown_write(stage, strlen(stage));
|
||||
test_teardown_write("\n", 1u);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1445,7 +1473,7 @@ TX_THREAD *thread_ptr;
|
||||
(UINT) _tx_thread_preempt_disable,
|
||||
(ULONG) _tx_thread_created_count);
|
||||
if (length > 0)
|
||||
(void) write(2, buffer, (size_t) length);
|
||||
test_teardown_write(buffer, (size_t) length);
|
||||
|
||||
for (core = 0; core < ((UINT) TX_THREAD_SMP_MAX_CORES); core++)
|
||||
{
|
||||
@@ -1457,7 +1485,7 @@ TX_THREAD *thread_ptr;
|
||||
test_teardown_thread_name(_tx_thread_current_ptr[core]),
|
||||
test_teardown_thread_name(_tx_thread_execute_ptr[core]));
|
||||
if (length > 0)
|
||||
(void) write(2, buffer, (size_t) length);
|
||||
test_teardown_write(buffer, (size_t) length);
|
||||
}
|
||||
|
||||
/* Walk the created list, stopping at the head, and after a fixed number of
|
||||
@@ -1476,10 +1504,10 @@ TX_THREAD *thread_ptr;
|
||||
thread_ptr -> tx_thread_inherit_priority,
|
||||
thread_ptr -> tx_thread_smp_core_mapped,
|
||||
(ULONG) thread_ptr -> tx_thread_smp_core_control,
|
||||
thread_ptr -> tx_thread_linux_deferred_preempt,
|
||||
thread_ptr -> tx_thread_linux_suspension_type);
|
||||
TEST_TEARDOWN_DEFERRED_PREEMPT(thread_ptr),
|
||||
TEST_TEARDOWN_SUSPENSION_TYPE(thread_ptr));
|
||||
if (length > 0)
|
||||
(void) write(2, buffer, (size_t) length);
|
||||
test_teardown_write(buffer, (size_t) length);
|
||||
|
||||
thread_ptr = thread_ptr -> tx_thread_created_next;
|
||||
if (thread_ptr == _tx_thread_created_ptr)
|
||||
@@ -1498,6 +1526,41 @@ static void test_teardown_abort(const char *reason)
|
||||
|
||||
|
||||
/* Watch an armed teardown, and only an armed one. */
|
||||
#ifdef _WIN32
|
||||
static unsigned __stdcall test_teardown_watchdog(void *input)
|
||||
{
|
||||
|
||||
ULONG waited_ms;
|
||||
ULONG limit_ms;
|
||||
|
||||
|
||||
(void) input;
|
||||
limit_ms = ((ULONG) test_teardown_timeout) * ((ULONG) 1000);
|
||||
waited_ms = ((ULONG) 0);
|
||||
|
||||
while (1)
|
||||
{
|
||||
|
||||
Sleep((DWORD) TEST_TEARDOWN_POLL_MS);
|
||||
|
||||
/* Nothing to watch while teardown is stood down. */
|
||||
if (test_teardown_armed == TX_FALSE)
|
||||
{
|
||||
waited_ms = ((ULONG) 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
waited_ms = waited_ms + ((ULONG) TEST_TEARDOWN_POLL_MS);
|
||||
if (waited_ms >= limit_ms)
|
||||
{
|
||||
test_teardown_abort("teardown did not complete");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0u;
|
||||
}
|
||||
#else
|
||||
static void *test_teardown_watchdog(void *input)
|
||||
{
|
||||
|
||||
@@ -1542,6 +1605,7 @@ ULONG limit_ms;
|
||||
|
||||
return input;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* Start the watchdog, stood down. Called before any test runs, so that no
|
||||
@@ -1549,7 +1613,11 @@ ULONG limit_ms;
|
||||
static void test_teardown_watchdog_start(void)
|
||||
{
|
||||
|
||||
#ifdef _WIN32
|
||||
uintptr_t watchdog_id;
|
||||
#else
|
||||
pthread_t watchdog_id;
|
||||
#endif
|
||||
char *value;
|
||||
|
||||
|
||||
@@ -1563,15 +1631,27 @@ char *value;
|
||||
/* Tracing wants the stages in the log next to the output around them,
|
||||
which block buffering would otherwise discard on a kill. */
|
||||
test_teardown_trace = TX_TRUE;
|
||||
#ifdef _WIN32
|
||||
setvbuf(stdout, TX_NULL, _IONBF, 0u);
|
||||
#else
|
||||
setvbuf(stdout, TX_NULL, _IOLBF, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* A timeout of zero turns the watchdog off. */
|
||||
if (test_teardown_timeout == ((UINT) 0))
|
||||
return;
|
||||
|
||||
#ifdef _WIN32
|
||||
watchdog_id = _beginthreadex(TX_NULL, 0u, test_teardown_watchdog, TX_NULL, 0u, TX_NULL);
|
||||
if (watchdog_id != ((uintptr_t) 0))
|
||||
{
|
||||
(void) CloseHandle((HANDLE) watchdog_id);
|
||||
}
|
||||
#else
|
||||
if (pthread_create(&watchdog_id, TX_NULL, test_teardown_watchdog, TX_NULL) == 0)
|
||||
pthread_detach(watchdog_id);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Some portions generated by Codex (gpt-5.6-sol).
|
||||
|
||||
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
|
||||
cmake_policy(SET CMP0057 NEW)
|
||||
|
||||
@@ -56,11 +58,22 @@ set(transition_sources
|
||||
# opposite, because calling the public names and letting tx_api.h decide what they
|
||||
# bind to is exactly what the TX_DISABLE_ERROR_CHECKING configuration is here to
|
||||
# demonstrate.
|
||||
if(MSVC)
|
||||
set(transition_kernel_compile_options
|
||||
"/FI${SOURCE_DIR}/threadx_thread_transition_host_test_port.h")
|
||||
set(transition_undefine_options /UTX_ENABLE_EVENT_TRACE
|
||||
/UTX_ENABLE_EVENT_LOG)
|
||||
else()
|
||||
set(transition_kernel_compile_options
|
||||
"-include;${SOURCE_DIR}/threadx_thread_transition_host_test_port.h")
|
||||
set(transition_undefine_options -UTX_ENABLE_EVENT_TRACE
|
||||
-UTX_ENABLE_EVENT_LOG)
|
||||
endif()
|
||||
|
||||
set_source_files_properties(
|
||||
${transition_kernel_sources}
|
||||
DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
||||
PROPERTIES COMPILE_OPTIONS
|
||||
"-include;${SOURCE_DIR}/threadx_thread_transition_host_test_port.h")
|
||||
PROPERTIES COMPILE_OPTIONS "${transition_kernel_compile_options}")
|
||||
|
||||
# Each entry is a test-name suffix, a colon, and the feature macros that define the
|
||||
# configuration, separated by "|". A semicolon cannot be used as that separator: it
|
||||
@@ -80,16 +93,20 @@ foreach(configuration ${transition_configurations})
|
||||
string(REPLACE "|" ";" configuration_macros ${configuration_macro_text})
|
||||
|
||||
set(test_name threadx_thread_transition_${configuration_name}_test)
|
||||
# Keep the internal target name short. CMake includes it in every object path,
|
||||
# and the descriptive test name can otherwise reach the legacy MSVC MAX_PATH
|
||||
# boundary in a normally nested Windows worktree.
|
||||
set(test_target tx_tt_${configuration_name})
|
||||
|
||||
add_executable(${test_name} ${transition_sources})
|
||||
add_executable(${test_target} ${transition_sources})
|
||||
|
||||
target_include_directories(
|
||||
${test_name}
|
||||
${test_target}
|
||||
PRIVATE ${SOURCE_DIR}
|
||||
${REPO_ROOT}/common/inc
|
||||
${REPO_ROOT}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc)
|
||||
|
||||
target_compile_definitions(${test_name} PRIVATE ${configuration_macros})
|
||||
target_compile_definitions(${test_target} PRIVATE ${configuration_macros})
|
||||
|
||||
# This directory is configured once per build configuration of the tree, so
|
||||
# these executables inherit whichever feature macros that configuration sets --
|
||||
@@ -103,13 +120,12 @@ foreach(configuration ${transition_configurations})
|
||||
# lost by turning tracing off here. Event logging is turned off for the same
|
||||
# reason; no configuration of the tree enables it.
|
||||
#
|
||||
# The -U flags have to reach the test source as well as the kernel sources,
|
||||
# The undefine options have to reach the test source as well as the kernel sources,
|
||||
# because TX_ENABLE_EVENT_TRACE is visible to tx_api.h and the two must agree on
|
||||
# what the headers declare. Target compile options land after the directory's
|
||||
# -D flags on the command line, which is what makes the -U effective.
|
||||
target_compile_options(${test_name} PRIVATE -UTX_ENABLE_EVENT_TRACE
|
||||
-UTX_ENABLE_EVENT_LOG)
|
||||
# definition flags on the command line, which is what makes them effective.
|
||||
target_compile_options(${test_target} PRIVATE ${transition_undefine_options})
|
||||
|
||||
add_test(${CMAKE_BUILD_TYPE}::${test_name} ${test_name})
|
||||
add_test(${CMAKE_BUILD_TYPE}::${test_name} ${test_target})
|
||||
|
||||
endforeach()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/***************************************************************************/
|
||||
|
||||
/* Some portions generated by Codex (gpt-5.6-sol). */
|
||||
/* Copyright (c) 2024 Microsoft Corporation */
|
||||
/* Copyright (c) 2026 Eclipse ThreadX contributors */
|
||||
/* */
|
||||
@@ -401,7 +403,7 @@ VOID (*temp_mutex_release)(TX_THREAD *thread_ptr);
|
||||
test_thread.tx_thread_timer.tx_timer_internal_list_head = TX_NULL;
|
||||
test_thread.tx_thread_suspending = TX_TRUE;
|
||||
test_thread.tx_thread_delayed_suspend = TX_TRUE;
|
||||
#if defined(_WIN64) || defined(TX_TIMER_EXTENSION_PTR_DEFINED)
|
||||
#if defined(_WIN32) || defined(TX_TIMER_EXTENSION_PTR_DEFINED)
|
||||
{
|
||||
TX_TIMER_INTERNAL timeout_timer;
|
||||
TX_TIMER_INTERNAL *saved_expired_timer_ptr;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: MIT and CC0-1.0
|
||||
**************************************************************************/
|
||||
// Some portions generated by Codex (gpt-5.6-sol).
|
||||
|
||||
/* This test drives the trace entry update paths -- the blocks guarded by
|
||||
TX_ENABLE_EVENT_TRACE that go back and patch a trace entry after the call
|
||||
@@ -73,7 +74,7 @@ static TX_SEMAPHORE semaphore_0;
|
||||
|
||||
static UCHAR trace_buffer[16384];
|
||||
|
||||
/* Four blocks of 20 bytes: 100 / (20 + sizeof(void *)) on a 32-bit build. */
|
||||
/* Enough storage for several 20-byte blocks on both 32-bit and 64-bit builds. */
|
||||
|
||||
static UCHAR block_pool_area[100];
|
||||
static UCHAR byte_pool_area[512];
|
||||
@@ -213,14 +214,22 @@ void *byte_ptr;
|
||||
UINT i;
|
||||
|
||||
|
||||
/* Empty the block pool. The first of these takes the immediate-success path
|
||||
through tx_block_allocate, which carries the first update block. */
|
||||
/* Empty the block pool. The first allocation takes the immediate-success
|
||||
path through tx_block_allocate, which carries the first update block.
|
||||
The exact capacity is port-dependent because each block has a pointer-
|
||||
sized header, so allocate until the pool reports that it is empty. */
|
||||
held_block = TX_NULL;
|
||||
for (i = 0; i < 4; i++)
|
||||
for (i = 0; i < 6u; i++)
|
||||
{
|
||||
|
||||
status = tx_block_allocate(&block_pool_0, &block_ptr, TX_NO_WAIT);
|
||||
|
||||
if (status == TX_NO_MEMORY)
|
||||
{
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (status != TX_SUCCESS)
|
||||
{
|
||||
|
||||
@@ -236,6 +245,12 @@ UINT i;
|
||||
}
|
||||
}
|
||||
|
||||
if ((status != TX_NO_MEMORY) || (held_block == TX_NULL))
|
||||
{
|
||||
|
||||
error++;
|
||||
}
|
||||
|
||||
/* The pool is empty now, so this suspends. It completes in thread 1's
|
||||
context when the block comes back, which is the second update block in
|
||||
tx_block_allocate -- and the suspend and resume it goes through carry the
|
||||
|
||||
Reference in New Issue
Block a user