summaryrefslogtreecommitdiff
path: root/test/tx/thread_transition/threadx_thread_transition_configuration_test.c
diff options
context:
space:
mode:
authorFrédéric Desbiens <[email protected]>2026-10-02 10:42:44 -0400
committerGitHub <[email protected]>2026-10-02 10:42:44 -0400
commit93387b0a60382a29579bbad5045c68328e3751df (patch)
tree42be790d602cb76f55090e5a7c418df7ea2b3428 /test/tx/thread_transition/threadx_thread_transition_configuration_test.c
parent44d7c95c582d415c4ad84527180b29c93c3bf664 (diff)
parent28281cd5647d4671236ca08d19bd18ed15b2265a (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/tx/thread_transition/threadx_thread_transition_configuration_test.c')
-rw-r--r--test/tx/thread_transition/threadx_thread_transition_configuration_test.c607
1 files changed, 607 insertions, 0 deletions
diff --git a/test/tx/thread_transition/threadx_thread_transition_configuration_test.c b/test/tx/thread_transition/threadx_thread_transition_configuration_test.c
new file mode 100644
index 00000000..fe6acb7e
--- /dev/null
+++ b/test/tx/thread_transition/threadx_thread_transition_configuration_test.c
@@ -0,0 +1,607 @@
+/***************************************************************************
+ * 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
+ **************************************************************************/
+
+
+/**************************************************************************/
+/**************************************************************************/
+/** */
+/** ThreadX Test */
+/** */
+/** Thread lifecycle transition configuration coverage */
+/** */
+/**************************************************************************/
+/**************************************************************************/
+
+/* The two configurations that matter to this change and that neither
+ regression tree builds are TX_NOT_INTERRUPTABLE and
+ TX_DISABLE_ERROR_CHECKING. This executable is built once for each
+ combination of them, against the four sources that change, and drives the
+ completion and termination transitions directly.
+
+ Why each one needs its own build rather than a case in the in-tree test:
+
+ - TX_NOT_INTERRUPTABLE selects a different transition. There the exit
+ notification callback runs with interrupts still disabled and the
+ interruptable suspension path -- which is what sets and clears the
+ transition marker in the configurations the trees do build -- is not
+ used at all, so the marker is set and cleared by explicit stores that
+ exist only in this configuration. A marker left set there would make a
+ normally completed thread permanently undeletable, which is the failure
+ mode worth more than a compile.
+
+ - TX_DISABLE_ERROR_CHECKING removes the _txe_ wrappers, so the public
+ names bind straight to the core services. The refusal has to live in
+ the core services for exactly that reason, and the way to demonstrate it
+ is to call the public names in a build that has no wrappers.
+
+ The scheduler is not linked. _tx_thread_system_ni_suspend and
+ _tx_thread_system_suspend are recorders, which is what makes the marker's
+ value at the moment of detachment directly observable -- and, in the
+ TX_NOT_INTERRUPTABLE completion path, the reason the marker has to be
+ cleared before the call rather than after it: on a real port that call
+ returns to the scheduler and never comes back.
+
+ Everything here is straight-line: there are no threads, no timers and no
+ waits, so there is nothing that can hang. */
+
+#include <stdio.h>
+#include <string.h>
+
+/* tx_api.h is included without TX_SOURCE_CODE, so that the public service names
+ are mapped the way an application sees them: to the _txe_ wrappers with error
+ checking on, and straight to the core services with it off. Letting the header
+ make that choice is the point -- the refusal has to survive the choice. Only
+ then is TX_SOURCE_CODE defined, for the internal declarations the recorders
+ below implement. */
+
+#include "tx_api.h"
+
+/* tx_thread_entry_exit_notify is both a service name and a TX_THREAD field name,
+ and it is the only name in this file that is both, so the mapping above would
+ rewrite the field assignment further down into a member that does not exist.
+ Dropping just that one mapping keeps the mapping that matters -- the one on the
+ three services this test calls -- while leaving the field reachable. The test
+ never calls the notify service; it plants the callback in the control block
+ directly, because the create path that would otherwise do it is not linked. */
+
+#undef tx_thread_entry_exit_notify
+
+#define TX_SOURCE_CODE
+#include "tx_thread.h"
+
+
+/* Counters the force-included port shim's macros drive. */
+
+unsigned int test_interrupt_disable_depth = 0;
+unsigned int test_interrupt_disable_max_depth = 0;
+unsigned int test_interrupt_restore_underflows = 0;
+unsigned int test_delete_port_completion_count = 0;
+unsigned int test_reset_port_completion_count = 0;
+
+
+#define TEST_STACK_BYTES 512
+
+static TX_THREAD victim;
+static TX_THREAD other;
+static UCHAR victim_stack[TEST_STACK_BYTES];
+
+/* What the recorders saw. */
+
+static unsigned int ni_suspend_calls = 0;
+static UINT ni_suspend_suspending = 0xFFFFFFFFU;
+static UINT ni_suspend_state = 0xFFFFFFFFU;
+static unsigned int ni_suspend_depth = 0xFFFFFFFFU;
+
+static unsigned int suspend_calls = 0;
+static UINT suspend_suspending = 0xFFFFFFFFU;
+
+static unsigned int stack_build_calls = 0;
+static unsigned int preempt_check_calls = 0;
+static unsigned int timer_deactivate_calls = 0;
+
+/* What the exit callback saw. */
+
+static unsigned int callback_calls = 0;
+static UINT callback_state = 0xFFFFFFFFU;
+static UINT callback_suspending = 0xFFFFFFFFU;
+static unsigned int callback_depth = 0xFFFFFFFFU;
+static UINT callback_delete_status = 0xFFFFFFFFU;
+static UINT callback_reset_status = 0xFFFFFFFFU;
+static UINT callback_delete_completions = 0xFFFFFFFFU;
+static UINT callback_reset_completions = 0xFFFFFFFFU;
+
+static unsigned int entry_calls = 0;
+
+static unsigned long expectations_checked = 0;
+static unsigned long expectations_failed = 0;
+
+
+static void expect(int condition, const char *what)
+{
+
+ expectations_checked++;
+
+ if (condition == 0)
+ {
+ expectations_failed++;
+ printf(" FAILED: %s\n", what);
+ }
+}
+
+
+static void expect_uint(UINT actual, UINT wanted, const char *what)
+{
+
+ expectations_checked++;
+
+ if (actual != wanted)
+ {
+ expectations_failed++;
+ printf(" FAILED: %s (got %lu, wanted %lu)\n", what,
+ (unsigned long) actual, (unsigned long) wanted);
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/* Recorders standing in for the scheduler. */
+/* ------------------------------------------------------------------------ */
+
+VOID _tx_thread_system_ni_suspend(TX_THREAD *thread_ptr, ULONG wait_option)
+{
+
+ (void) wait_option;
+
+ ni_suspend_calls++;
+ ni_suspend_suspending = thread_ptr -> tx_thread_suspending;
+ ni_suspend_state = thread_ptr -> tx_thread_state;
+ ni_suspend_depth = test_interrupt_disable_depth;
+}
+
+
+VOID _tx_thread_system_suspend(TX_THREAD *thread_ptr)
+{
+
+ suspend_calls++;
+ suspend_suspending = thread_ptr -> tx_thread_suspending;
+
+ /* The interruptable suspension path is where the marker is cleared in the
+ configurations the regression trees build, so the recorder clears it as
+ the real one does, on the same condition. */
+ if (thread_ptr -> tx_thread_suspending == TX_TRUE)
+ {
+ thread_ptr -> tx_thread_suspending = TX_FALSE;
+ }
+
+ /* It also decrements the preemption disable count, which its callers have
+ raised on its behalf. The recorder has to do the same, or the balance
+ this test asserts across the whole transition would be off by one for a
+ reason that belongs to the recorder rather than to the code under
+ test. */
+ _tx_thread_preempt_disable--;
+}
+
+
+VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*function_ptr)(VOID))
+{
+
+ (void) thread_ptr;
+ (void) function_ptr;
+
+ stack_build_calls++;
+}
+
+
+VOID _tx_thread_system_preempt_check(VOID)
+{
+ preempt_check_calls++;
+}
+
+
+/* _tx_thread_terminate deactivates the target's timeout timer, and
+ _txe_thread_reset refuses a call made from the timer thread. Neither the
+ timer component nor the timer thread is linked, so both are supplied here:
+ the deactivation as a recorder, and the timer thread as a control block that
+ is never the current thread, so the wrapper's check is exercised and takes
+ the not-the-timer-thread branch. */
+
+VOID _tx_timer_system_deactivate(TX_TIMER_INTERNAL *timer_ptr)
+{
+
+ (void) timer_ptr;
+
+ timer_deactivate_calls++;
+}
+
+
+TX_THREAD _tx_timer_thread;
+
+
+/* ------------------------------------------------------------------------ */
+/* The thread under test. */
+/* ------------------------------------------------------------------------ */
+
+static void reset_records(void)
+{
+
+ ni_suspend_calls = 0;
+ ni_suspend_suspending = 0xFFFFFFFFU;
+ ni_suspend_state = 0xFFFFFFFFU;
+ ni_suspend_depth = 0xFFFFFFFFU;
+ suspend_calls = 0;
+ suspend_suspending = 0xFFFFFFFFU;
+ stack_build_calls = 0;
+ preempt_check_calls = 0;
+ timer_deactivate_calls = 0;
+ callback_calls = 0;
+ callback_state = 0xFFFFFFFFU;
+ callback_suspending = 0xFFFFFFFFU;
+ callback_depth = 0xFFFFFFFFU;
+ callback_delete_status = 0xFFFFFFFFU;
+ callback_reset_status = 0xFFFFFFFFU;
+ callback_delete_completions = 0xFFFFFFFFU;
+ callback_reset_completions = 0xFFFFFFFFU;
+ entry_calls = 0;
+
+ test_interrupt_disable_depth = 0;
+ test_interrupt_disable_max_depth = 0;
+ test_interrupt_restore_underflows = 0;
+ test_delete_port_completion_count = 0;
+ test_reset_port_completion_count = 0;
+}
+
+
+static VOID victim_entry(ULONG input)
+{
+ (void) input;
+ entry_calls++;
+}
+
+
+static VOID exit_notify(TX_THREAD *thread_ptr, UINT type)
+{
+
+ if (type != TX_THREAD_EXIT)
+ {
+ return;
+ }
+
+ callback_calls++;
+ callback_state = thread_ptr -> tx_thread_state;
+ callback_suspending = thread_ptr -> tx_thread_suspending;
+ callback_depth = test_interrupt_disable_depth;
+
+ /* The public names. With error checking on these are the _txe_ wrappers;
+ with it off they are the core services. Either way the refusal has to
+ come back. */
+ callback_delete_status = tx_thread_delete(thread_ptr);
+ callback_reset_status = tx_thread_reset(thread_ptr);
+
+ /* A refused delete or reset must not have run the port's completion
+ processing, which on a real port tears the thread's port resources
+ down. */
+ callback_delete_completions = test_delete_port_completion_count;
+ callback_reset_completions = test_reset_port_completion_count;
+}
+
+
+/* Put the victim's control block into the shape the kernel would have left it
+ in after tx_thread_create, for the fields the four sources under test read.
+ tx_thread_create itself is not linked, because the scheduler it resumes into
+ is not linked either. */
+
+static void make_victim(UINT state)
+{
+
+ memset(&victim, 0, sizeof(victim));
+ memset(victim_stack, 0xEF, sizeof(victim_stack));
+
+ victim.tx_thread_id = TX_THREAD_ID;
+ victim.tx_thread_state = state;
+ victim.tx_thread_suspending = TX_FALSE;
+ victim.tx_thread_entry = victim_entry;
+ victim.tx_thread_entry_parameter = 0;
+#ifndef TX_DISABLE_NOTIFY_CALLBACKS
+
+ /* The field itself does not exist when notification is compiled out. */
+ victim.tx_thread_entry_exit_notify = exit_notify;
+#endif
+ victim.tx_thread_stack_start = victim_stack;
+ victim.tx_thread_stack_size = sizeof(victim_stack);
+ victim.tx_thread_priority = 16;
+ victim.tx_thread_preempt_threshold = 16;
+ victim.tx_thread_suspend_cleanup = TX_NULL;
+
+ /* A created list with the victim and one other thread on it, so that
+ delete takes the link-up-the-neighbours branch rather than the
+ only-thread branch. */
+ memset(&other, 0, sizeof(other));
+ other.tx_thread_id = TX_THREAD_ID;
+ other.tx_thread_state = TX_SUSPENDED;
+ other.tx_thread_created_next = &victim;
+ other.tx_thread_created_previous = &victim;
+
+ victim.tx_thread_created_next = &other;
+ victim.tx_thread_created_previous = &other;
+
+ _tx_thread_created_ptr = &victim;
+ _tx_thread_created_count = 2;
+
+ _tx_thread_current_ptr = &victim;
+ _tx_thread_execute_ptr = &victim;
+ _tx_thread_preempt_disable = 0;
+ _tx_thread_system_state = 0;
+}
+
+
+static void scenario_completion(void)
+{
+
+ printf("Completion transition:\n");
+
+ reset_records();
+ make_victim(TX_SUSPENDED);
+
+ /* _tx_thread_shell_entry picks the thread up from the current pointer,
+ calls its entry function, and then runs the completion transition. */
+ _tx_thread_shell_entry();
+
+ expect_uint((UINT) entry_calls, 1, "the entry function ran exactly once");
+
+#ifndef TX_DISABLE_NOTIFY_CALLBACKS
+
+ expect_uint((UINT) callback_calls, 1, "the exit callback ran exactly once");
+
+ /* The terminal state is published before the callback. */
+ expect_uint(callback_state, TX_COMPLETED, "the callback saw TX_COMPLETED");
+
+ /* And the transition is marked while the callback runs, in this
+ configuration as in the interruptable one. */
+ expect_uint(callback_suspending, TX_TRUE, "the callback saw the transition marked");
+
+#ifdef TX_NOT_INTERRUPTABLE
+
+ /* In this configuration the callback runs inside the interrupt lock. */
+ expect(callback_depth > 0, "the callback ran with interrupts disabled");
+#else
+ expect_uint((UINT) callback_depth, 0, "the callback ran with interrupts enabled");
+#endif
+
+ /* The two refusals, from whichever of the wrapper and the core service
+ this build binds the public name to. */
+ expect_uint(callback_delete_status, TX_DELETE_ERROR, "the callback's delete was refused");
+ expect_uint(callback_reset_status, TX_NOT_DONE, "the callback's reset was refused");
+
+ /* A refused delete or reset ran no port completion processing. */
+ expect_uint(callback_delete_completions, 0, "the refused delete ran no port completion");
+ expect_uint(callback_reset_completions, 0, "the refused reset ran no port completion");
+#endif
+
+ /* The detachment saw the marker in the state that keeps the thread
+ deletable afterwards. This is the failure mode the configuration exists
+ to catch: on a real port the suspension call below returns to the
+ scheduler and does not come back, so a marker cleared after it rather
+ than before it would stay set for ever and the completed thread could
+ never be deleted. */
+#ifdef TX_NOT_INTERRUPTABLE
+
+ expect_uint((UINT) ni_suspend_calls, 1, "the non-interruptable suspension ran once");
+ expect_uint(ni_suspend_state, TX_COMPLETED, "it was entered with the terminal state");
+ expect_uint(ni_suspend_suspending, TX_FALSE,
+ "the marker was already cleared when the suspension was entered");
+ expect(ni_suspend_depth > 0, "the suspension was entered with interrupts disabled");
+ expect_uint((UINT) suspend_calls, 0, "the interruptable suspension was not used");
+#else
+
+ expect_uint((UINT) suspend_calls, 1, "the interruptable suspension ran once");
+ expect_uint(suspend_suspending, TX_TRUE,
+ "the marker was still set when the suspension was entered, for it to clear");
+ expect_uint((UINT) ni_suspend_calls, 0, "the non-interruptable suspension was not used");
+#endif
+
+ /* Either way the thread is deletable once the transition has finished. */
+ expect_uint(victim.tx_thread_suspending, TX_FALSE, "the marker is clear afterwards");
+ expect_uint(tx_thread_delete(&victim), TX_SUCCESS, "the completed thread deletes afterwards");
+ expect_uint((UINT) test_delete_port_completion_count, 1,
+ "the successful delete ran the port completion once");
+
+ /* And the interrupt lock is balanced across the whole transition. */
+ expect_uint((UINT) test_interrupt_disable_depth, 0, "the interrupt lock is balanced");
+ expect_uint((UINT) test_interrupt_restore_underflows, 0, "no unmatched interrupt restore");
+}
+
+
+static void scenario_terminate_ready(void)
+{
+
+ printf("Termination of a ready thread:\n");
+
+ reset_records();
+ make_victim(TX_READY);
+
+ /* Terminate a target that is not the current thread, so that the reset the
+ callback attempts is refused by the transition check alone rather than
+ by the current-thread rule. */
+ _tx_thread_current_ptr = &other;
+
+ expect_uint(tx_thread_terminate(&victim), TX_SUCCESS, "the termination succeeded");
+
+#ifndef TX_DISABLE_NOTIFY_CALLBACKS
+
+ expect_uint((UINT) callback_calls, 1, "the exit callback ran exactly once");
+ expect_uint(callback_state, TX_TERMINATED, "the callback saw TX_TERMINATED");
+ expect_uint(callback_suspending, TX_TRUE, "the callback saw the transition marked");
+ expect_uint(callback_delete_status, TX_DELETE_ERROR, "the callback's delete was refused");
+ expect_uint(callback_reset_status, TX_NOT_DONE, "the callback's reset was refused");
+ expect_uint(callback_reset_completions, 0, "the refused reset ran no port completion");
+ expect_uint((UINT) stack_build_calls, 0, "the refused reset rebuilt no stack");
+#endif
+
+#ifdef TX_NOT_INTERRUPTABLE
+
+ expect_uint((UINT) ni_suspend_calls, 1, "the non-interruptable suspension ran once");
+ expect(callback_depth > 0, "the callback ran with interrupts disabled");
+#else
+
+ expect_uint((UINT) suspend_calls, 1, "the interruptable suspension ran once");
+#endif
+
+ /* The marker is cleared in the common tail, after the callback and after
+ the mutex-release processing. */
+ expect_uint(victim.tx_thread_suspending, TX_FALSE, "the marker is clear afterwards");
+ expect_uint(victim.tx_thread_state, TX_TERMINATED, "the state is terminal afterwards");
+ expect_uint((UINT) _tx_thread_preempt_disable, 0, "the preempt disable count is balanced");
+ expect_uint((UINT) test_interrupt_disable_depth, 0, "the interrupt lock is balanced");
+ expect_uint((UINT) test_interrupt_restore_underflows, 0, "no unmatched interrupt restore");
+
+ expect_uint(tx_thread_delete(&victim), TX_SUCCESS, "the terminated thread deletes afterwards");
+}
+
+
+static void scenario_terminate_suspended(void)
+{
+
+UINT reset_status;
+
+
+ printf("Termination of a suspended thread:\n");
+
+ reset_records();
+ make_victim(TX_SUSPENDED);
+ _tx_thread_current_ptr = &other;
+
+ expect_uint(tx_thread_terminate(&victim), TX_SUCCESS, "the termination succeeded");
+
+#ifndef TX_DISABLE_NOTIFY_CALLBACKS
+
+ expect_uint((UINT) callback_calls, 1, "the exit callback ran exactly once");
+ expect_uint(callback_state, TX_TERMINATED, "the callback saw TX_TERMINATED");
+
+ /* The non-ready branch used to clear the marker before this point. */
+ expect_uint(callback_suspending, TX_TRUE, "the callback saw the transition marked");
+ expect_uint(callback_delete_status, TX_DELETE_ERROR, "the callback's delete was refused");
+ expect_uint(callback_reset_status, TX_NOT_DONE, "the callback's reset was refused");
+ expect_uint((UINT) stack_build_calls, 0, "the refused reset rebuilt no stack");
+#endif
+
+ /* This branch never calls a suspension routine: the thread was already off
+ the ready list. */
+ expect_uint((UINT) ni_suspend_calls, 0, "no non-interruptable suspension on this branch");
+ expect_uint((UINT) suspend_calls, 0, "no interruptable suspension on this branch");
+
+ expect_uint(victim.tx_thread_suspending, TX_FALSE, "the marker is clear afterwards");
+ expect_uint((UINT) _tx_thread_preempt_disable, 0, "the preempt disable count is balanced");
+ expect_uint((UINT) test_interrupt_disable_depth, 0, "the interrupt lock is balanced");
+ expect_uint((UINT) test_interrupt_restore_underflows, 0, "no unmatched interrupt restore");
+
+ /* The documented lifecycle is available afterwards, on this branch too. */
+ reset_status = tx_thread_reset(&victim);
+ expect_uint(reset_status, TX_SUCCESS, "the terminated thread resets afterwards");
+ expect_uint(victim.tx_thread_state, TX_SUSPENDED, "the reset left it suspended");
+ expect_uint((UINT) stack_build_calls, 1, "the successful reset rebuilt the stack once");
+ expect_uint((UINT) test_reset_port_completion_count, 1,
+ "the successful reset ran the port completion once");
+
+ expect_uint(tx_thread_delete(&victim), TX_DELETE_ERROR, "a reset thread is not deletable");
+ expect_uint(tx_thread_terminate(&victim), TX_SUCCESS, "it terminates again");
+ expect_uint(tx_thread_delete(&victim), TX_SUCCESS, "and then deletes");
+}
+
+
+static void scenario_marker_not_set_outside_transition(void)
+{
+
+ printf("Outside a transition:\n");
+
+ reset_records();
+ make_victim(TX_SUSPENDED);
+ _tx_thread_current_ptr = &other;
+
+ /* Neither state is terminal, so both services refuse for the reason they
+ always did, and the marker plays no part. */
+ expect_uint(victim.tx_thread_suspending, TX_FALSE, "the marker is clear on a live thread");
+ expect_uint(tx_thread_delete(&victim), TX_DELETE_ERROR, "delete refuses a suspended thread");
+ expect_uint(tx_thread_reset(&victim), TX_NOT_DONE, "reset refuses a suspended thread");
+
+ /* A terminal state with the marker clear is the ordinary deletable case,
+ which is what the refusal must not have broken. */
+ victim.tx_thread_state = TX_COMPLETED;
+ victim.tx_thread_suspending = TX_FALSE;
+ expect_uint(tx_thread_reset(&victim), TX_SUCCESS, "reset accepts a settled completed thread");
+
+ victim.tx_thread_state = TX_TERMINATED;
+ expect_uint(tx_thread_delete(&victim), TX_SUCCESS, "delete accepts a settled terminated thread");
+
+ /* And a terminal state with the marker set is the new refusal, tested
+ directly rather than through a transition, so that both services are
+ covered on both terminal states. */
+ make_victim(TX_COMPLETED);
+ victim.tx_thread_suspending = TX_TRUE;
+ _tx_thread_current_ptr = &other;
+ expect_uint(tx_thread_delete(&victim), TX_DELETE_ERROR, "delete refuses a marked completed thread");
+ expect_uint(tx_thread_reset(&victim), TX_NOT_DONE, "reset refuses a marked completed thread");
+
+ victim.tx_thread_state = TX_TERMINATED;
+ expect_uint(tx_thread_delete(&victim), TX_DELETE_ERROR, "delete refuses a marked terminated thread");
+ expect_uint(tx_thread_reset(&victim), TX_NOT_DONE, "reset refuses a marked terminated thread");
+
+ /* Clearing the marker makes both available again, which is the whole
+ claim about the refusal being transient. */
+ victim.tx_thread_suspending = TX_FALSE;
+ expect_uint(tx_thread_reset(&victim), TX_SUCCESS, "clearing the marker restores reset");
+ victim.tx_thread_state = TX_TERMINATED;
+ expect_uint(tx_thread_delete(&victim), TX_SUCCESS, "clearing the marker restores delete");
+}
+
+
+int main(void)
+{
+
+ printf("Thread lifecycle transition configuration test: ");
+
+#ifdef TX_NOT_INTERRUPTABLE
+ printf("TX_NOT_INTERRUPTABLE ");
+#else
+ printf("interruptable ");
+#endif
+#ifdef TX_DISABLE_ERROR_CHECKING
+ printf("TX_DISABLE_ERROR_CHECKING ");
+#else
+ printf("error checking ");
+#endif
+#ifdef TX_DISABLE_NOTIFY_CALLBACKS
+ printf("TX_DISABLE_NOTIFY_CALLBACKS");
+#else
+ printf("notify callbacks");
+#endif
+ printf("\n");
+
+ scenario_completion();
+ scenario_terminate_ready();
+ scenario_terminate_suspended();
+ scenario_marker_not_set_outside_transition();
+
+ printf("%lu of %lu expectations passed.\n",
+ expectations_checked - expectations_failed, expectations_checked);
+
+ if (expectations_failed != 0)
+ {
+ printf("FAILED: %lu expectations.\n", expectations_failed);
+ return(1);
+ }
+
+ printf("SUCCESS!\n");
+ return(0);
+}