diff options
| author | Frédéric Desbiens <[email protected]> | 2026-10-02 10:42:44 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-10-02 10:42:44 -0400 |
| commit | 93387b0a60382a29579bbad5045c68328e3751df (patch) | |
| tree | 42be790d602cb76f55090e5a7c418df7ea2b3428 /test/posix | |
| parent | 44d7c95c582d415c4ad84527180b29c93c3bf664 (diff) | |
| parent | 28281cd5647d4671236ca08d19bd18ed15b2265a (diff) | |
Merge pull request #783 from eclipse-threadx/devv6.5.2.202603_rel
Merged changes ahead of the v6.5.2.202603 release
Diffstat (limited to 'test/posix')
| -rw-r--r-- | test/posix/cmake/CMakeLists.txt | 83 | ||||
| -rw-r--r-- | test/posix/cmake/regression/CMakeLists.txt | 65 | ||||
| -rwxr-xr-x | test/posix/cmake/run.sh | 189 | ||||
| -rw-r--r-- | test/posix/regression/posix_mq_basic_test.c | 159 | ||||
| -rw-r--r-- | test/posix/regression/posix_mq_send_abort_test.c | 273 | ||||
| -rw-r--r-- | test/posix/regression/posix_pthread_basic_test.c | 192 | ||||
| -rw-r--r-- | test/posix/regression/posix_pthread_self_test.c | 177 |
7 files changed, 1138 insertions, 0 deletions
diff --git a/test/posix/cmake/CMakeLists.txt b/test/posix/cmake/CMakeLists.txt new file mode 100644 index 00000000..830e69ba --- /dev/null +++ b/test/posix/cmake/CMakeLists.txt @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) +cmake_policy(SET CMP0054 NEW) +cmake_policy(SET CMP0057 NEW) + +# Regression tests for the POSIX compatibility layer. +# +# The layer cannot run on the hosted simulation ports: it supplies its own +# pthread.h, errno.h, time.h and friends, which collide with the C library's, +# and the linux port emulates threads with the very pthread calls the layer +# exports. These tests therefore run on emulated targets, reusing the RISC-V +# BSP and QEMU setup that the ThreadX RISC-V regression suite already uses. + +project(threadx_posix_test LANGUAGES C ASM) + +set(BUILD_CONFIGURATIONS default_build disable_notify_callbacks_build + stack_checking_build trace_build) +set(CMAKE_CONFIGURATION_TYPES + ${BUILD_CONFIGURATIONS} + CACHE STRING "list of supported configuration types" FORCE) +set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + ${CMAKE_CONFIGURATION_TYPES}) +list(GET CMAKE_CONFIGURATION_TYPES 0 BUILD_TYPE) +if((NOT CMAKE_BUILD_TYPE) OR (NOT ("${CMAKE_BUILD_TYPE}" IN_LIST + CMAKE_CONFIGURATION_TYPES))) + set(CMAKE_BUILD_TYPE + "${BUILD_TYPE}" + CACHE STRING "Build Type of the project" FORCE) +endif() + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS "Using toolchain file: ${CMAKE_TOOLCHAIN_FILE}.") +message(STATUS "THREADX_ARCH: ${THREADX_ARCH}") + +set(default_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32) +set(disable_notify_callbacks_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_DISABLE_NOTIFY_CALLBACKS) +set(stack_checking_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_ENABLE_STACK_CHECKING) +set(trace_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_ENABLE_EVENT_TRACE) + +# These tests are 32-bit only for now, and the reason is a gap in the layer +# rather than a choice about coverage. +# +# The layer passes a message by putting the address of a private buffer into the +# queue. ULONG is 32 bits on every ThreadX port, including the 64-bit ones, so +# on a 64-bit target that address only fits if TX_64_BIT is defined: the layer +# then splits it across two message words and sizes the message accordingly. +# Without it, px_mq_send.c truncates the pointer to 32 bits and px_mq_receive.c +# casts the truncated value back, which the compiler reports only as a warning. +# +# Defining TX_64_BIT is not a way out either: tx_api.h then uses the extension +# pointer macros, which need tx_thread_extension_ptr in the thread control +# block, and no 64-bit port outside ports_smp and ports/linux declares it. +# Closing that gap belongs with the layer, not with its tests. +if(THREADX_ARCH STREQUAL "risc-v64") + message(FATAL_ERROR + "The POSIX layer cannot run on a 64-bit port yet: it needs TX_64_BIT to " + "carry a 64-bit message pointer, and TX_64_BIT needs a port that declares " + "tx_thread_extension_ptr. Use the riscv32 toolchain file for these tests.") +endif() + +add_compile_options( + -std=c99 + -O0 + -g3 + -fdiagnostics-color + -Werror + -ffunction-sections + -fdata-sections + -DTX_REGRESSION_TEST + -DTEST_STACK_SIZE_PRINTF=4096 + -DEXTERNAL_EXIT + ${${CMAKE_BUILD_TYPE}} +) + +enable_testing() + +# ThreadX itself, plus the posix-threadx target the top-level file defines. +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../.. threadx) + +# Reuse the RISC-V BSP from the ThreadX regression suite rather than keeping a +# second copy of the board support in this tree. +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../tx/cmake/riscv/bsp riscv_bsp) + +add_subdirectory(regression) diff --git a/test/posix/cmake/regression/CMakeLists.txt b/test/posix/cmake/regression/CMakeLists.txt new file mode 100644 index 00000000..8f433bcb --- /dev/null +++ b/test/posix/cmake/regression/CMakeLists.txt @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) +cmake_policy(SET CMP0057 NEW) + +project(posix_regression_test LANGUAGES C ASM) + +set(SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../../regression) +set(TX_TEST_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../tx/regression) +set(BSP_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../tx/cmake/riscv/bsp) + +if(THREADX_ARCH STREQUAL "risc-v64") + set(QEMU_SYSTEM "qemu-system-riscv64") + set(XLEN_TAG "rv64") +else() + set(QEMU_SYSTEM "qemu-system-riscv32") + set(XLEN_TAG "rv32") +endif() + +set(posix_test_cases + ${SOURCE_DIR}/posix_mq_basic_test.c + ${SOURCE_DIR}/posix_mq_send_abort_test.c + ${SOURCE_DIR}/posix_pthread_basic_test.c + ${SOURCE_DIR}/posix_pthread_self_test.c +) + +set(WEAK_DEFAULTS_SOURCE ${CMAKE_CURRENT_LIST_DIR}/../../../shared/regression/testcontrol_weak_defaults.c) + +# Weak defaults must be an OBJECT library so all objects are always linked into +# each test executable. A STATIC library would have the linker skip them, since +# weak definitions do not satisfy undefined strong references while scanning an +# archive. +add_library(posix_test_weak_defaults OBJECT ${WEAK_DEFAULTS_SOURCE}) +target_compile_definitions(posix_test_weak_defaults PRIVATE + CTEST BATCH_TEST EXTERNAL_EXIT TEST_STACK_SIZE_PRINTF=4096) +target_compile_options(posix_test_weak_defaults PRIVATE -include stdlib.h) +target_link_libraries(posix_test_weak_defaults PRIVATE azrtos::threadx) + +# testcontrol.c supplies main(), tx_application_define() and +# test_control_return(), and is shared with the ThreadX regression suite. +add_library(posix_test_utility ${TX_TEST_DIR}/testcontrol.c) +target_link_libraries(posix_test_utility PUBLIC riscv_bsp azrtos::threadx) +target_compile_definitions(posix_test_utility PUBLIC + CTEST BATCH_TEST EXTERNAL_EXIT TEST_STACK_SIZE_PRINTF=4096) +target_compile_options(posix_test_utility PUBLIC -include stdlib.h) + +function(register_posix_test test_name) + target_link_options(${test_name} PRIVATE + -T ${BSP_DIR}/link.lds -nostartfiles + -Wl,--gc-sections) + add_test( + NAME ${CMAKE_BUILD_TYPE}::${XLEN_TAG}::${test_name} + COMMAND ${QEMU_SYSTEM} -nographic -smp 1 -bios none -m 128M + -machine virt -kernel $<TARGET_FILE:${test_name}> + ) + set_tests_properties(${CMAKE_BUILD_TYPE}::${XLEN_TAG}::${test_name} + PROPERTIES TIMEOUT 120) +endfunction() + +foreach(test_case ${posix_test_cases}) + get_filename_component(test_name ${test_case} NAME_WE) + add_executable(${test_name} ${test_case} $<TARGET_OBJECTS:posix_test_weak_defaults>) + target_link_libraries(${test_name} PRIVATE + -Wl,--whole-archive riscv_bsp -Wl,--no-whole-archive + posix_test_utility posix-threadx) + register_posix_test(${test_name}) +endforeach() diff --git a/test/posix/cmake/run.sh b/test/posix/cmake/run.sh new file mode 100755 index 00000000..76536cf7 --- /dev/null +++ b/test/posix/cmake/run.sh @@ -0,0 +1,189 @@ +#!/bin/bash +############################################################################## +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). +# The AI-generated portions may be considered public domain (CC0-1.0) +# and not subject to the project's licence. The human contributor has +# reviewed and verified that the code is correct. +# +# SPDX-License-Identifier: MIT and CC0-1.0 +############################################################################## + + +set -e + +# POSIX compatibility layer regression test build/test runner. +# Usage: run.sh <arch> <build|test> [all|<config> ...] +# arch: riscv32 +# +# Only riscv32 is available: the layer cannot carry a 64-bit message pointer +# without TX_64_BIT, and TX_64_BIT needs a port that declares +# tx_thread_extension_ptr. See the comment in ../CMakeLists.txt. + +function help() { + echo "Usage: $0 <arch> <command> [all|<build_configuration> ...]" + echo "" + echo " arch: riscv32" + echo " command: build | test" + echo "" + echo "Available build configurations:" + for build in ${build_configurations[*]}; do + echo " $build" + done + exit 1 +} + +function validate() { + for build in ${build_configurations[*]}; do + if [ "$1" == "$build" ]; then + return + fi + done + help +} + +function generate() { + local arch=$1 + local build=$2 + local toolchain_file + local build_dir + + toolchain_file="${REPO_ROOT}/cmake/riscv32_gnu.cmake" + + build_dir="build/${arch}_${build}" + cmake -B"$build_dir" -GNinja \ + -DCMAKE_TOOLCHAIN_FILE="$toolchain_file" \ + -DCMAKE_BUILD_TYPE="$build" \ + . +} + +function build() { + local arch=$1 + local build=$2 + # -k 0 keeps Ninja going after a target fails, so one broken target no + # longer decides whether the targets after it exist. ctest reports a + # missing binary as a failing test, which turned a single link error into + # a failure count that varied with build scheduling order. + cmake --build "build/${arch}_${build}" -- -k 0 +} + +function run_test() { + local arch=$1 + local build=$2 + local parallel_jobs=$3 + + # Guard the pushd: with the caller capturing this function's status, set -e + # no longer aborts here, so a missing build directory would otherwise let + # ctest run in the source tree and report "no tests" as success. + pushd "build/${arch}_${build}" || return 1 + [ -z "${CTEST_PARALLEL_LEVEL}" ] && parallel="-j${parallel_jobs}" + if [ -z "${CTEST_REPEAT_FAIL}" ]; then + repeat_fail=2 + else + repeat_fail=${CTEST_REPEAT_FAIL} + fi + # ctest's status is captured and returned past the popd and the summary + # steps, which all succeed. Without this the function would return 0 and a + # failing configuration would be reported as a pass. + local status=0 + ctest $parallel --timeout 1000 \ + -O "${arch}_${build}.txt" \ + -T test --no-compress-output \ + --test-output-size-passed 4194304 \ + --test-output-size-failed 4194304 \ + --output-on-failure \ + --repeat until-pass:${repeat_fail} \ + --output-junit "${arch}_${build}.xml" || status=$? + popd + return $status +} + +# Determine repo root and script directory +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" +cd "$SCRIPT_DIR" + +# Parse build configurations from CMakeLists.txt +result=$(sed -n "/(BUILD_CONFIGURATIONS/,/)/p" CMakeLists.txt | sed ':label;N;s/\n/ /;b label' | grep -Pzo "[a-zA-Z0-9_]*build[a-zA-Z0-9_]*\s*" | tr -d '\0') +IFS=' ' +read -ra build_configurations <<< "$result" + +if [ $# -lt 2 ]; then + help +fi + +arch=$1 +shift + +if [ "$arch" != "riscv32" ]; then + echo "Error: arch must be 'riscv32'" + help +fi + +command=$1 +shift + +if [ "$#" == "0" ]; then + builds=${build_configurations[0]} +elif [ "$*" == "all" ]; then + builds=${build_configurations[@]} +else + for item in $*; do + validate "$item" + done + builds=$* +fi + +if [ "$command" == "build" ]; then + for item in $builds; do + generate "$arch" "$item" + echo "" + done + + # A failing configuration must not stop the ones after it: under set -e + # the loop would abort and leave them unbuilt, which then reads as a wall + # of missing-binary test failures. The status is accumulated and returned. + build_status=0 + for item in $builds; do + echo "Building ${arch} ${item}" + build "$arch" "$item" || build_status=$? + echo "" + done + [ $build_status -eq 0 ] || exit $build_status +elif [ "$command" == "test" ]; then + cores=$(nproc) + if [ -z "${CTEST_PARALLEL_LEVEL}" ]; then + build_counts=$(echo $builds | wc -w) + parallel_jobs=$(($cores / $build_counts)) + parallel_jobs=$(($parallel_jobs + 2)) + pids="" + for item in $builds; do + echo "Testing ${arch} ${item}" + run_test "$arch" "$item" "$parallel_jobs" & + pids+=" $!" + done + exit_code=0 + for p in $pids; do + wait $p || exit_code=$? + done + exit $exit_code + else + # ctest's status is accumulated rather than allowed to abort the loop. + # set -e would otherwise stop at the first failing configuration and + # leave every configuration after it untested. This mirrors what the + # parallel branch above already does with wait. + exit_code=0 + for item in $builds; do + echo "Testing ${arch} ${item}" + run_test "$arch" "$item" "$parallel_jobs" || exit_code=$? + done + exit $exit_code + fi +else + help +fi diff --git a/test/posix/regression/posix_mq_basic_test.c b/test/posix/regression/posix_mq_basic_test.c new file mode 100644 index 00000000..2ecb1ac8 --- /dev/null +++ b/test/posix/regression/posix_mq_basic_test.c @@ -0,0 +1,159 @@ +/***************************************************************************/ +/* Copyright (c) 2026 Eclipse ThreadX contributors */ +/* */ +/* This program and the accompanying materials are made available under */ +/* the terms of the MIT License which is available at */ +/* https://opensource.org/licenses/MIT. */ +/* */ +/* AI Disclosure: This file was largely AI-generated by Claude Code */ +/* (Opus 5). The AI-generated portions may be considered public domain */ +/* (CC0-1.0) and not subject to the project's licence. The human */ +/* contributor has reviewed and verified that the code is correct. */ +/* */ +/* SPDX-License-Identifier: MIT and CC0-1.0 */ +/***************************************************************************/ + +/* This test is designed to test basic POSIX message queue operation: open a + queue, send a message through it, receive the message back and confirm both + the contents and the priority survived the round trip. */ + +#include <stdio.h> +#include "tx_api.h" +#include "pthread.h" + +#define TEST_MESSAGE_SIZE 40 +#define TEST_QUEUE_DEPTH 10 +#define TEST_PRIORITY 3 + +/* Memory the POSIX layer carves its own objects out of. */ +static ULONG posix_region[192 * 1024 / sizeof(ULONG)]; + +static pthread_t worker; +static pthread_attr_t worker_attr; +static mqd_t queue_des; +static struct mq_attr queue_attr; + +extern void test_control_return(UINT status); + +static VOID *worker_entry(VOID *input); + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void posix_mq_basic_application_define(void *first_unused_memory) +#endif +{ + +struct sched_param param; +VOID *storage_ptr; + + (void) first_unused_memory; + + /* Bring the POSIX layer up. It returns the first address beyond its own + structures, which is where pthread stacks are taken from. */ + storage_ptr = posix_initialize(posix_region); + + queue_attr.mq_maxmsg = TEST_QUEUE_DEPTH; + queue_attr.mq_msgsize = TEST_MESSAGE_SIZE; + + queue_des = mq_open("mq_basic", O_CREAT | O_RDWR, 0, &queue_attr); + if (queue_des == (mqd_t) ERROR) + { + + printf("Running POSIX Message Queue Basic Test............................. ERROR #1\n"); + test_control_return(1); + } + + pthread_attr_init(&worker_attr); + param.sched_priority = 10; + pthread_attr_setschedparam(&worker_attr, ¶m); + pthread_attr_setstackaddr(&worker_attr, storage_ptr); + + if (pthread_create(&worker, &worker_attr, worker_entry, NULL) != OK) + { + + printf("Running POSIX Message Queue Basic Test............................. ERROR #2\n"); + test_control_return(1); + } +} + + +static VOID *worker_entry(VOID *input) +{ + +CHAR sent[TEST_MESSAGE_SIZE]; +CHAR received[TEST_MESSAGE_SIZE]; +ULONG received_priority; +ssize_t received_length; +INT i; + + (void) input; + + printf("Running POSIX Message Queue Basic Test............................. "); + + /* Build a recognisable payload rather than a constant one, so a message + that is dropped or truncated cannot pass by accident. */ + for (i = 0; i < TEST_MESSAGE_SIZE; i++) + { + + sent[i] = (CHAR) (i + 1); + received[i] = (CHAR) 0; + } + + if (mq_send(queue_des, sent, TEST_MESSAGE_SIZE, TEST_PRIORITY) != OK) + { + + printf("ERROR #3\n"); + test_control_return(1); + } + + received_priority = 0; + received_length = mq_receive(queue_des, received, TEST_MESSAGE_SIZE, &received_priority); + + if (received_length != (ssize_t) TEST_MESSAGE_SIZE) + { + + printf("ERROR #4\n"); + test_control_return(1); + } + + if (received_priority != (ULONG) TEST_PRIORITY) + { + + printf("ERROR #5\n"); + test_control_return(1); + } + + for (i = 0; i < TEST_MESSAGE_SIZE; i++) + { + + if (received[i] != sent[i]) + { + + printf("ERROR #6\n"); + test_control_return(1); + } + } + + if (mq_close(queue_des) != OK) + { + + printf("ERROR #7\n"); + test_control_return(1); + } + + if (mq_unlink("mq_basic") != OK) + { + + printf("ERROR #8\n"); + test_control_return(1); + } + + printf("SUCCESS!\n"); + test_control_return(0); + + return (NULL); +} diff --git a/test/posix/regression/posix_mq_send_abort_test.c b/test/posix/regression/posix_mq_send_abort_test.c new file mode 100644 index 00000000..a4e7e7e6 --- /dev/null +++ b/test/posix/regression/posix_mq_send_abort_test.c @@ -0,0 +1,273 @@ +/***************************************************************************/ +/* Copyright (c) 2026 Eclipse ThreadX contributors */ +/* */ +/* This program and the accompanying materials are made available under */ +/* the terms of the MIT License which is available at */ +/* https://opensource.org/licenses/MIT. */ +/* */ +/* AI Disclosure: This file was largely AI-generated by Claude Code */ +/* (Opus 5). The AI-generated portions may be considered public domain */ +/* (CC0-1.0) and not subject to the project's licence. The human */ +/* contributor has reviewed and verified that the code is correct. */ +/* */ +/* SPDX-License-Identifier: MIT and CC0-1.0 */ +/***************************************************************************/ + +/* This test covers the message buffer mq_send() used to leak when the send + failed, reported as issue #568. + + mq_send() allocates a private buffer from the queue's own byte pool, copies + the caller's message into it and passes the address through the queue. The + receiver owns the buffer from then on and releases it. When the send itself + fails the message never reaches a receiver, so mq_send() has to release the + buffer before returning or it is lost. + + The failure is provoked the way an application would meet it: the queue is + filled, a sender blocks on it, and its wait is aborted. The queue survives, + so any buffer that is not released stays lost. Aborting repeatedly and + watching the queue's byte pool separates the two outcomes: with the buffer + released the pool returns to its original size, and without it the pool + shrinks by a message every time. */ + +#include <stdio.h> +#include "tx_api.h" +#include "pthread.h" + +/* The queue's pool is sized (mq_maxmsg + 1) * (mq_msgsize + 11), with a floor + of 100 bytes, so it holds barely more than a full queue. Keep the messages + small and the queue shallow: that floor then leaves room for a few leaked + buffers to accumulate and be measured, instead of exhausting the pool on the + first one and hanging the sender inside posix_internal_error(). */ +#define TEST_MESSAGE_SIZE 8 +#define TEST_QUEUE_DEPTH 1 +#define TEST_ABORT_COUNT 2 + +static ULONG posix_region[192 * 1024 / sizeof(ULONG)]; + +static pthread_t sender; +static pthread_attr_t sender_attr; +static pthread_t controller; +static pthread_attr_t controller_attr; + +static mqd_t queue_des; +static struct mq_attr queue_attr; + +/* Written by the sender, read by the controller. */ +static TX_THREAD * volatile sender_thread_ptr; +static volatile INT send_failures; +static volatile INT stop_sending; + +extern void test_control_return(UINT status); + +static VOID *sender_entry(VOID *input); +static VOID *controller_entry(VOID *input); + + +/* Report how many bytes the queue's message pool has free. */ +static ULONG pool_available(void) +{ + +ULONG available; + + available = 0; + tx_byte_pool_info_get(&(queue_des -> f_data -> vq_message_area), + TX_NULL, &available, TX_NULL, TX_NULL, TX_NULL, TX_NULL); + + return (available); +} + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void posix_mq_send_abort_application_define(void *first_unused_memory) +#endif +{ + +struct sched_param param; +VOID *storage_ptr; + + (void) first_unused_memory; + + storage_ptr = posix_initialize(posix_region); + + queue_attr.mq_maxmsg = TEST_QUEUE_DEPTH; + queue_attr.mq_msgsize = TEST_MESSAGE_SIZE; + + queue_des = mq_open("mq_abort", O_CREAT | O_RDWR, 0, &queue_attr); + if (queue_des == (mqd_t) ERROR) + { + + printf("Running POSIX Message Queue Send Abort Test....................... ERROR #1\n"); + test_control_return(1); + } + + /* The controller drives the test and runs above the sender, so it keeps + control while the sender is being unblocked. */ + pthread_attr_init(&controller_attr); + param.sched_priority = 15; + pthread_attr_setschedparam(&controller_attr, ¶m); + pthread_attr_setstackaddr(&controller_attr, storage_ptr); + storage_ptr = (ULONG *) storage_ptr + (4096 / sizeof(ULONG)); + + pthread_attr_init(&sender_attr); + param.sched_priority = 10; + pthread_attr_setschedparam(&sender_attr, ¶m); + pthread_attr_setstackaddr(&sender_attr, storage_ptr); + + if (pthread_create(&controller, &controller_attr, controller_entry, NULL) != OK) + { + + printf("Running POSIX Message Queue Send Abort Test....................... ERROR #2\n"); + test_control_return(1); + } + + if (pthread_create(&sender, &sender_attr, sender_entry, NULL) != OK) + { + + printf("Running POSIX Message Queue Send Abort Test....................... ERROR #3\n"); + test_control_return(1); + } +} + + +/* Sends until told to stop. Every send blocks, because the controller keeps + the queue full, and every one is released by an aborted wait. */ +static VOID *sender_entry(VOID *input) +{ + +CHAR message[TEST_MESSAGE_SIZE]; +INT i; + + (void) input; + + for (i = 0; i < TEST_MESSAGE_SIZE; i++) + { + + message[i] = (CHAR) i; + } + + /* Publish our own thread pointer so the controller can abort our wait + without casting a pthread ID back to a ThreadX thread. */ + sender_thread_ptr = tx_thread_identify(); + + while (stop_sending == 0) + { + + if (mq_send(queue_des, message, TEST_MESSAGE_SIZE, 1) != OK) + { + + send_failures++; + } + } + + return (NULL); +} + + +static VOID *controller_entry(VOID *input) +{ + +CHAR message[TEST_MESSAGE_SIZE]; +ULONG baseline; +ULONG after; +ULONG lost; +INT i; + + (void) input; + + printf("Running POSIX Message Queue Send Abort Test....................... "); + + for (i = 0; i < TEST_MESSAGE_SIZE; i++) + { + + message[i] = (CHAR) i; + } + + /* Fill the queue so that any further send has to wait. */ + if (mq_send(queue_des, message, TEST_MESSAGE_SIZE, 1) != OK) + { + + printf("ERROR #4\n"); + test_control_return(1); + } + + /* Let the sender reach its first blocking send, then take the baseline. + The sender is holding one allocation at this point, which is the state + every later measurement is compared against. */ + tx_thread_sleep(2); + + if (sender_thread_ptr == TX_NULL) + { + + printf("ERROR #5\n"); + test_control_return(1); + } + + baseline = pool_available(); + + for (i = 0; i < TEST_ABORT_COUNT; i++) + { + + /* Release the sender from its wait. Its send fails, and the buffer it + allocated has to come back to the pool. */ + if (tx_thread_wait_abort(sender_thread_ptr) != TX_SUCCESS) + { + + printf("ERROR #6\n"); + test_control_return(1); + } + + /* Give the sender time to return from mq_send() and block again. */ + tx_thread_sleep(2); + } + + if (send_failures != TEST_ABORT_COUNT) + { + + printf("ERROR #7\n"); + test_control_return(1); + } + + /* The sender is blocked again holding exactly one allocation, the same as + at the baseline, so the pool should read the same. */ + after = pool_available(); + + if (after > baseline) + { + + printf("ERROR #8\n"); + test_control_return(1); + } + + lost = baseline - after; + + /* Anything approaching a message means buffers are not coming back. A + leak would cost TEST_ABORT_COUNT messages, so this separates the two + cases comfortably without depending on byte pool overhead. */ + if (lost >= (ULONG) TEST_MESSAGE_SIZE) + { + + printf("ERROR #9\n"); + test_control_return(1); + } + + /* Shut the sender down and confirm the queue still works. */ + stop_sending = 1; + tx_thread_wait_abort(sender_thread_ptr); + tx_thread_sleep(2); + + if (mq_receive(queue_des, message, TEST_MESSAGE_SIZE, TX_NULL) != (ssize_t) TEST_MESSAGE_SIZE) + { + + printf("ERROR #10\n"); + test_control_return(1); + } + + printf("SUCCESS!\n"); + test_control_return(0); + + return (NULL); +} diff --git a/test/posix/regression/posix_pthread_basic_test.c b/test/posix/regression/posix_pthread_basic_test.c new file mode 100644 index 00000000..afb8d4f3 --- /dev/null +++ b/test/posix/regression/posix_pthread_basic_test.c @@ -0,0 +1,192 @@ +/***************************************************************************/ +/* Copyright (c) 2026 Eclipse ThreadX contributors */ +/* */ +/* This program and the accompanying materials are made available under */ +/* the terms of the MIT License which is available at */ +/* https://opensource.org/licenses/MIT. */ +/* */ +/* AI Disclosure: This file was largely AI-generated by Claude Code */ +/* (Opus 5). The AI-generated portions may be considered public domain */ +/* (CC0-1.0) and not subject to the project's licence. The human */ +/* contributor has reviewed and verified that the code is correct. */ +/* */ +/* SPDX-License-Identifier: MIT and CC0-1.0 */ +/***************************************************************************/ + +/* This test covers basic pthread and synchronisation operation: a pthread runs + and reports its own identity, a mutex serialises access to shared state, a + semaphore signals between two pthreads, and a join collects the worker's exit + value. */ + +#include <stdio.h> +#include "tx_api.h" +/* The layer ships no semaphore.h; sem_t and the sem_* calls arrive through + pthread.h, which pulls in tx_posix.h. */ +#include "pthread.h" + +#define TEST_STACK_BYTES 4096 + +static ULONG posix_region[192 * 1024 / sizeof(ULONG)]; + +static pthread_t worker; +static pthread_attr_t worker_attr; +static pthread_t driver; +static pthread_attr_t driver_attr; + +static pthread_mutex_t guard; +static sem_t handoff; + +static volatile INT shared_counter; +static pthread_t worker_self_id; + +static INT worker_result = 77; + +extern void test_control_return(UINT status); + +static VOID *worker_entry(VOID *input); +static VOID *driver_entry(VOID *input); + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void posix_pthread_basic_application_define(void *first_unused_memory) +#endif +{ + +struct sched_param param; +VOID *storage_ptr; + + (void) first_unused_memory; + + storage_ptr = posix_initialize(posix_region); + + if (pthread_mutex_init(&guard, NULL) != OK) + { + + printf("Running POSIX Pthread Basic Test.................................. ERROR #1\n"); + test_control_return(1); + } + + if (sem_init(&handoff, 0, 0) != OK) + { + + printf("Running POSIX Pthread Basic Test.................................. ERROR #2\n"); + test_control_return(1); + } + + /* The driver runs below the worker so that the worker reaches its first + blocking point before the driver inspects anything. */ + pthread_attr_init(&worker_attr); + param.sched_priority = 15; + pthread_attr_setschedparam(&worker_attr, ¶m); + pthread_attr_setstackaddr(&worker_attr, storage_ptr); + storage_ptr = (ULONG *) storage_ptr + (TEST_STACK_BYTES / sizeof(ULONG)); + + pthread_attr_init(&driver_attr); + param.sched_priority = 10; + pthread_attr_setschedparam(&driver_attr, ¶m); + pthread_attr_setstackaddr(&driver_attr, storage_ptr); + + if (pthread_create(&worker, &worker_attr, worker_entry, NULL) != OK) + { + + printf("Running POSIX Pthread Basic Test.................................. ERROR #3\n"); + test_control_return(1); + } + + if (pthread_create(&driver, &driver_attr, driver_entry, NULL) != OK) + { + + printf("Running POSIX Pthread Basic Test.................................. ERROR #4\n"); + test_control_return(1); + } +} + + +static VOID *worker_entry(VOID *input) +{ + +INT i; + + (void) input; + + /* Record what this pthread believes its own ID to be, for the driver to + compare against the ID pthread_create() handed back. */ + worker_self_id = pthread_self(); + + for (i = 0; i < 100; i++) + { + + pthread_mutex_lock(&guard); + shared_counter++; + pthread_mutex_unlock(&guard); + } + + /* Hand control to the driver. */ + sem_post(&handoff); + + return ((VOID *) &worker_result); +} + + +static VOID *driver_entry(VOID *input) +{ + +VOID *exit_value; + + (void) input; + + printf("Running POSIX Pthread Basic Test.................................. "); + + /* Wait for the worker to finish its counting. */ + if (sem_wait(&handoff) != OK) + { + + printf("ERROR #5\n"); + test_control_return(1); + } + + if (shared_counter != 100) + { + + printf("ERROR #6\n"); + test_control_return(1); + } + + if (worker_self_id != worker) + { + + printf("ERROR #7\n"); + test_control_return(1); + } + + exit_value = NULL; + if (pthread_join(worker, &exit_value) != OK) + { + + printf("ERROR #8\n"); + test_control_return(1); + } + + if (exit_value != (VOID *) &worker_result) + { + + printf("ERROR #9\n"); + test_control_return(1); + } + + if (pthread_mutex_destroy(&guard) != OK) + { + + printf("ERROR #10\n"); + test_control_return(1); + } + + printf("SUCCESS!\n"); + test_control_return(0); + + return (NULL); +} diff --git a/test/posix/regression/posix_pthread_self_test.c b/test/posix/regression/posix_pthread_self_test.c new file mode 100644 index 00000000..7cbb6450 --- /dev/null +++ b/test/posix/regression/posix_pthread_self_test.c @@ -0,0 +1,177 @@ +/***************************************************************************/ +/* Copyright (c) 2026 Eclipse ThreadX contributors */ +/* */ +/* This program and the accompanying materials are made available under */ +/* the terms of the MIT License which is available at */ +/* https://opensource.org/licenses/MIT. */ +/* */ +/* AI Disclosure: This file was largely AI-generated by Claude Code */ +/* (Opus 5). The AI-generated portions may be considered public domain */ +/* (CC0-1.0) and not subject to the project's licence. The human */ +/* contributor has reviewed and verified that the code is correct. */ +/* */ +/* SPDX-License-Identifier: MIT and CC0-1.0 */ +/***************************************************************************/ + +/* This test covers pthread_self() being called from a thread that is not a + pthread. + + Nothing stops an application mixing tx_thread_create() with the POSIX layer, + and a ThreadX thread created that way has no POSIX control block. + posix_thread2tcb() returns NULL for it, and pthread_self() used to read the + pthread ID through that null pointer, then read the signal fields of a + POSIX_TCB out of a plain TX_THREAD that never had any. + + The test calls pthread_self() from both kinds of thread: a real pthread, + which must get the ID pthread_create() handed back, and a plain ThreadX + thread, which must get zero rather than a fault or a value read out of + whatever follows its control block. */ + +#include <stdio.h> +#include "tx_api.h" +#include "pthread.h" + +#define TEST_STACK_BYTES 4096 + +static ULONG posix_region[192 * 1024 / sizeof(ULONG)]; + +static pthread_t worker; +static pthread_attr_t worker_attr; + +/* A plain ThreadX thread, deliberately not created through pthread_create(). */ +static TX_THREAD plain_thread; + +static volatile INT worker_done; +static volatile INT plain_done; +static pthread_t worker_reported_id; +static pthread_t plain_reported_id; + +extern void test_control_return(UINT status); + +static VOID *worker_entry(VOID *input); +static VOID plain_entry(ULONG input); +static VOID checker_entry(ULONG input); + +static TX_THREAD checker_thread; + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void posix_pthread_self_application_define(void *first_unused_memory) +#endif +{ + +struct sched_param param; +VOID *storage_ptr; +CHAR *pointer; + + pointer = (CHAR *) first_unused_memory; + + storage_ptr = posix_initialize(posix_region); + + /* A genuine pthread. */ + pthread_attr_init(&worker_attr); + param.sched_priority = 15; + pthread_attr_setschedparam(&worker_attr, ¶m); + pthread_attr_setstackaddr(&worker_attr, storage_ptr); + + if (pthread_create(&worker, &worker_attr, worker_entry, NULL) != OK) + { + + printf("Running POSIX Pthread Self Test.................................. ERROR #1\n"); + test_control_return(1); + } + + /* A ThreadX thread that the POSIX layer knows nothing about. */ + if (tx_thread_create(&plain_thread, "plain threadx thread", plain_entry, 0, + pointer, TEST_STACK_BYTES, + 20, 20, TX_NO_TIME_SLICE, TX_AUTO_START) != TX_SUCCESS) + { + + printf("Running POSIX Pthread Self Test.................................. ERROR #2\n"); + test_control_return(1); + } + pointer = pointer + TEST_STACK_BYTES; + + /* Runs last and inspects what the other two reported. */ + if (tx_thread_create(&checker_thread, "checker", checker_entry, 0, + pointer, TEST_STACK_BYTES, + 25, 25, TX_NO_TIME_SLICE, TX_AUTO_START) != TX_SUCCESS) + { + + printf("Running POSIX Pthread Self Test.................................. ERROR #3\n"); + test_control_return(1); + } +} + + +static VOID *worker_entry(VOID *input) +{ + + (void) input; + + worker_reported_id = pthread_self(); + worker_done = 1; + + return (NULL); +} + + +/* The case that used to fault: a ThreadX thread asking for its pthread ID. */ +static VOID plain_entry(ULONG input) +{ + + (void) input; + + plain_reported_id = pthread_self(); + plain_done = 1; +} + + +static VOID checker_entry(ULONG input) +{ + + (void) input; + + printf("Running POSIX Pthread Self Test.................................. "); + + /* Both threads run above this one, so they have finished by now. */ + if ((worker_done == 0) || (plain_done == 0)) + { + + printf("ERROR #4\n"); + test_control_return(1); + } + + /* A pthread must report the ID pthread_create() produced. */ + if (worker_reported_id != worker) + { + + printf("ERROR #5\n"); + test_control_return(1); + } + + /* A pthread ID is the address of its control block, so a real one is never + zero. */ + if (worker_reported_id == (pthread_t) 0) + { + + printf("ERROR #6\n"); + test_control_return(1); + } + + /* The plain ThreadX thread has no pthread ID, and must be told so rather + than handed something read out of memory that follows its TX_THREAD. */ + if (plain_reported_id != (pthread_t) 0) + { + + printf("ERROR #7\n"); + test_control_return(1); + } + + printf("SUCCESS!\n"); + test_control_return(0); +} |
