Document the dynamic text fixtures, utility functions, and the interrupt test support.
Update #3199. --- eng/index.rst | 4 +- eng/test-framework.rst | 185 ++++++++++++++++++++++++++++++++-- images/eng/interrupt-test.odg | Bin 0 -> 14829 bytes images/eng/interrupt-test.pdf | Bin 0 -> 14153 bytes images/eng/interrupt-test.png | Bin 0 -> 75272 bytes 5 files changed, 178 insertions(+), 11 deletions(-) create mode 100644 images/eng/interrupt-test.odg create mode 100644 images/eng/interrupt-test.pdf create mode 100644 images/eng/interrupt-test.png diff --git a/eng/index.rst b/eng/index.rst index 8f91c5e..f6b02ec 100644 --- a/eng/index.rst +++ b/eng/index.rst @@ -11,8 +11,8 @@ RTEMS Software Engineering (|version|) .. topic:: Copyrights and License - | |copy| 2018, 2019 embedded brains GmbH - | |copy| 2018, 2019 Sebastian Huber + | |copy| 2018, 2020 embedded brains GmbH + | |copy| 2018, 2020 Sebastian Huber | |copy| 1988, 2015 On-Line Applications Research Corporation (OAR) .. include:: ../common/license.rst diff --git a/eng/test-framework.rst b/eng/test-framework.rst index b6411b5..582718d 100644 --- a/eng/test-framework.rst +++ b/eng/test-framework.rst @@ -1,7 +1,7 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 -.. Copyright (C) 2018, 2019 embedded brains GmbH -.. Copyright (C) 2018, 2019 Sebastian Huber +.. Copyright (C) 2018, 2020 embedded brains GmbH +.. Copyright (C) 2018, 2020 Sebastian Huber Software Test Framework *********************** @@ -144,13 +144,41 @@ macro followed by a function body: The test case `name` must be a valid C designator. The test case names must be unique within the test suite. The `fixture` must point to a statically -initialized read-only object of type `T_fixture`. The test fixture -provides methods to setup, stop and tear down a test case. A context is passed -to the methods. The initial context is defined by the read-only fixture -object. The context can be obtained by the `T_fixture_context()` -function. It can be set within the scope of one test case by the -`T_set_fixture_context()` function. This can be used for example to -dynamically allocate a test environment in the setup method. +initialized read-only object of type `T_fixture`. + +.. code-block:: c + + typedef struct T_fixture { + void (*setup)(void *context); + void (*stop)(void *context); + void (*teardown)(void *context); + void (*scope)(void *context, char *buffer, size_t size); + void *initial_context; + } T_fixture; + +The test fixture provides methods to setup, stop, and teardown a test case as +well as to give the scope for log messags. A context is passed to each of the +methods. The initial context is defined by the read-only fixture object. The +context can be obtained by the `T_fixture_context()` function. It can be set +within the scope of one test case by the `T_set_fixture_context()` function. +This can be used for example to dynamically allocate a test environment in the +setup method. + +The test case fixtures of a test case are organized as a stack. Fixtures can +be dynamically added to a test case and removed from a test case via the +`T_push_fixture()` and `T_pop_fixture()` functions. + +.. code-block:: c + + void *T_push_fixture(T_fixture_node *node, const T_fixture *fixture); + + void T_pop_fixture(void); + +The `T_push_fixture()` function needs an uninitialized fixture node which must +exist until `T_pop_fixture()` is called. It returns the initial context of the +fixture. At the end of a test case all pushed fixtures are popped +automatically. A call of `T_pop_fixture()` invokes the teardown method of the +fixture and must correspond to a previous call to `T_push_fixture()`. .. code-block:: c :caption: Test Fixture Example @@ -1028,6 +1056,34 @@ RTEMS, floating-point operations are only supported in special tasks and may be forbidden in interrupt context. The formatted output functions provided by the test framework work in every context. +Utility +------- + +You can stop a test case via the ``T_stop()`` function. This function does not +return. You can indicate unreachable code paths with the ``T_unreachable()`` +function. If this function is called, then the test case stops. + +You can busy wait with the ``T_busy()`` function: + +.. code-block:: c + + void T_busy(uint_fast32_t count); + +It performs a busy loop with the specified iteration count. This function is +optimized to not perform memory accesses and should have a small jitter. + +You can get an interation count for the ``T_busy()`` function which corresponds +roughly to one clock tick interval with the ``T_get_one_clock_tick_busy()`` +function: + +.. code-block:: c + + uint_fast32_t T_get_one_clock_tick_busy(void); + +This function requires a clock driver. It must be called from thread context +with interrupts enabled. It may return a different value each time it is +called. + Time Services ------------- @@ -1353,6 +1409,117 @@ reported. M:E:Empty:D:0.015188063 E:measure_empty:N:1:F:0:D:14.284869 +Interrupt Tests +--------------- + +In the operating system implementation you may have two kinds of critical +sections. Firstly, there are low-level critical sections protected by +interrupts disabled and maybe also some SMP spin lock. Secondly, there are +high-level critical sections which are protected by disabled thread +dispatching. The high-level critical sections may contain several low-level +critical sections. Between these low-level critical sections interrupts may +happen which could alter the code path taken in the high-level critical +section. + +The test framework provides support to write test cases for high-level critical +sections though the `T_interrupt_test()` function: + +.. code-block:: c + + typedef enum { + T_INTERRUPT_TEST_INITIAL, + T_INTERRUPT_TEST_ACTION, + T_INTERRUPT_TEST_BLOCKED, + T_INTERRUPT_TEST_CONTINUE, + T_INTERRUPT_TEST_DONE, + T_INTERRUPT_TEST_EARLY, + T_INTERRUPT_TEST_INTERRUPT, + T_INTERRUPT_TEST_LATE, + T_INTERRUPT_TEST_TIMEOUT + } T_interrupt_test_state; + + typedef struct { + void (*prepare)(void *arg); + void (*action)(void *arg); + T_interrupt_test_state (*interrupt)(void *arg); + void (*blocked)(void *arg); + uint32_t max_iteration_count; + } T_interrupt_test_config; + + T_interrupt_test_state T_interrupt_test( + const T_interrupt_test_config *config, + void *arg + ); + +This function returns ``T_INTERRUPT_TEST_DONE`` if the test condition was +satisfied within the maximum iteration count, otherwise it returns +``T_INTERRUPT_TEST_TIMEOUT``. The interrupt test run uses the specified +configuration and passes the specified argument to all configured handlers. +The function shall be called from thread context with interrupts enabled. + +.. image:: ../images/eng/interrupt-test.* + :scale: 60 + :align: center + +The optional *prepare* handler should prepare the system so that the *action* +handler can be called. It is called in a tight loop, so all the time consuming +setup should be done before ``T_interrupt_test()`` is called. During the +preparation the test state is ``T_INTERRUPT_TEST_INITIAL``. The preparation +handler shall not change the test state. + +The *action* handler should call the function which executes the code section +under test. The execution path up to the code section under test should have a +low jitter. Otherwise, the adaptive interrupt time point algorithm may not +find the right spot. + +The *interrupt* handler should check if the test condition is satisfied or a +new iteration is necessary. This handler is called in interrupt context. It +shall return ``T_INTERRUPT_TEST_DONE`` if the test condition is satisfied and +the test run is done. It shall return ``T_INTERRUPT_TEST_EARLY`` if the +interrupt happened too early to satisfy the test condition. It shall return +``T_INTERRUPT_TEST_LATE`` if the interrupt happened too late to satisfy the +test condition. It shall return ``T_INTERRUPT_TEST_CONTINUE`` if the test +should continue with the current timing settings. Other states shall not be +returned. It is critical to return the early and late states if the test +conditions was not satisfied, otherwise the adaptive bisection algorithm may +not work. The returned state is used to try to change the test state from +``T_INTERRUPT_TEST_ACTION`` to the returned state. + +The optional *blocked* handler is invoked if the executing thread blocks during +the action processing. It should remove the blocking condition of the thread +so that the next iteration can start. It can use +``T_interrupt_change_state()`` to change the interrupt test state. + +The *max iteration count* configuration member defines the maximum iteration +count of the test loop. If the maximum iteration count is reached before the +test condition is satisfied, then ``T_interrupt_test()`` returns +``T_INTERRUPT_TEST_TIMEOUT``. + +The *interrupt* and *blocked* handlers may be called in arbitrary test states. + +The *action*, *interrupt*, and *blocked* handlers can use +``T_interrupt_test_get_state()`` to get the current test state: + +.. code-block:: c + + T_interrupt_test_state T_interrupt_test_get_state(void); + +The *action*, *interrupt*, and *blocked* handlers can use +``T_interrupt_test_change_state()`` to try to change the test state from an +expected state to a desired state: + +.. code-block:: c + + T_interrupt_test_state T_interrupt_test_change_state( + T_interrupt_test_state expected_state, + T_interrupt_test_state desired_state + ); + +The function returns the previous state. If it **differs from the expected +state**, then the requested state **change to the desired state did not take +place**. In an SMP configuration, do not call this function in a tight loop. +It could lock up the test run. To busy wait for a state change, use +``T_interrupt_test_get_state()``. Test Runner ----------- -- 2.26.2 _______________________________________________ devel mailing list devel@rtems.org http://lists.rtems.org/mailman/listinfo/devel