/*
 * FreeRTOS V202212.00
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */

/*
 * This file contains some test scenarios that ensure tasks respond correctly
 * to xTaskAbortDelay() calls.  It also ensures tasks return the correct state
 * of eBlocked when blocked indefinitely in both the case where a task is
 * blocked on an object and when a task is blocked on a notification.
 */

/* Standard includes. */
#include "limits.h"

/* Kernel includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
#include "stream_buffer.h"

/* Demo includes. */
#include "AbortDelay.h"

/* This file can only be used if the functionality it tests is included in the
 * build.  Remove the whole file if this is not the case. */
#if ( INCLUDE_xTaskAbortDelay == 1 )

    #if ( INCLUDE_xTaskGetHandle != 1 )
        #error This test file uses the xTaskGetHandle() API function so INCLUDE_xTaskGetHandle must be set to 1 in FreeRTOSConfig.h.
    #endif

/* Task priorities.  Allow these to be overridden. */
    #ifndef abtCONTROLLING_PRIORITY
        #define abtCONTROLLING_PRIORITY    ( configMAX_PRIORITIES - 3 )
    #endif

    #ifndef abtBLOCKING_PRIORITY
        #define abtBLOCKING_PRIORITY    ( configMAX_PRIORITIES - 2 )
    #endif

/* The tests that are performed. */
    #define abtNOTIFY_WAIT_ABORTS       0
    #define abtNOTIFY_TAKE_ABORTS       1
    #define abtDELAY_ABORTS             2
    #define abtDELAY_UNTIL_ABORTS       3
    #define abtSEMAPHORE_TAKE_ABORTS    4
    #define abtEVENT_GROUP_ABORTS       5
    #define abtQUEUE_SEND_ABORTS        6
    #define abtSTREAM_BUFFER_RECEIVE    7
    #define abtMAX_TESTS                8

/*-----------------------------------------------------------*/

/*
 * The two test tasks.  The controlling task specifies which test to executed.
 * More information is provided in the comments within the tasks.
 */
    static void prvControllingTask( void * pvParameters );
    static void prvBlockingTask( void * pvParameters );

/*
 * Test functions called by the blocking task.  Each function follows the same
 * pattern, but the way the task blocks is different in each case.
 *
 * In each function three blocking calls are made.  The first and third
 * blocking call is expected to time out, while the middle blocking call is
 * expected to be aborted by the controlling task half way through the block
 * time.
 */
    static void prvTestAbortingTaskNotifyWait( void );
    static void prvTestAbortingTaskNotifyTake( void );
    static void prvTestAbortingTaskDelay( void );
    static void prvTestAbortingTaskDelayUntil( void );
    static void prvTestAbortingSemaphoreTake( void );
    static void prvTestAbortingEventGroupWait( void );
    static void prvTestAbortingQueueSend( void );
    static void prvTestAbortingStreamBufferReceive( void );

/*
 * Performs a few tests to cover code paths not otherwise covered by the continuous
 * tests.
 */
    static void prvPerformSingleTaskTests( void );

/*
 * Checks the amount of time a task spent in the Blocked state is within the
 * expected bounds.
 */
    static void prvCheckExpectedTimeIsWithinAnAcceptableMargin( TickType_t xStartTime,
                                                                TickType_t xExpectedBlockTime );

/*-----------------------------------------------------------*/

/* Used to ensure that tasks are still executing without error. */
    static volatile BaseType_t xControllingCycles = 0, xBlockingCycles = 0;
    static volatile BaseType_t xErrorOccurred = pdFALSE;

/* Each task needs to know the other tasks handle so they can send signals to
 * each other.  The handle is obtained from the task's name. */
    static const char * pcControllingTaskName = "AbtCtrl", * pcBlockingTaskName = "AbtBlk";

/* The maximum amount of time a task will block for. */
    const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 100 );
    const TickType_t xHalfMaxBlockTime = pdMS_TO_TICKS( 50 );

/* The actual block time is dependent on the priority of other tasks in the
 * system so the actual block time might be greater than that expected, but it
 * should be within an acceptable upper bound. */
    const TickType_t xAllowableMargin = pdMS_TO_TICKS( 7 );

/*-----------------------------------------------------------*/

    void vCreateAbortDelayTasks( void )
    {
        /* Create the two test tasks described above. */
        xTaskCreate( prvControllingTask, pcControllingTaskName, configMINIMAL_STACK_SIZE, NULL, abtCONTROLLING_PRIORITY, NULL );
        xTaskCreate( prvBlockingTask, pcBlockingTaskName, configMINIMAL_STACK_SIZE, NULL, abtBLOCKING_PRIORITY, NULL );
    }
/*-----------------------------------------------------------*/

    static void prvControllingTask( void * pvParameters )
    {
        TaskHandle_t xBlockingTask;
        uint32_t ulTestToPerform = abtNOTIFY_WAIT_ABORTS;
        TickType_t xTimeAtStart;
        const TickType_t xStartMargin = 2UL;

        /* Just to remove compiler warnings. */
        ( void ) pvParameters;

        xBlockingTask = xTaskGetHandle( pcBlockingTaskName );
        configASSERT( xBlockingTask );

        for( ; ; )
        {
            /* Tell the secondary task to perform the next test. */
            xTimeAtStart = xTaskGetTickCount();
            xTaskNotify( xBlockingTask, ulTestToPerform, eSetValueWithOverwrite );

            /* The secondary task has a higher priority, so will now be in the
             * Blocked state to wait for a maximum of xMaxBlockTime.  It expects that
             * period to complete with a timeout.  It will then block for
             * xMaxBlockTimeAgain, but this time it expects to the block time to abort
             * half way through.  Block until it is time to send the abort to the
             * secondary task.  xStartMargin is used because this task takes timing
             * from the beginning of the test, whereas the blocking task takes timing
             * from the entry into the Blocked state - and as the tasks run at
             * different priorities, there may be some discrepancy.  Also, temporarily
             * raise the priority of the controlling task to that of the blocking
             * task to minimise discrepancies. */
            vTaskPrioritySet( NULL, abtBLOCKING_PRIORITY );
            vTaskDelay( xMaxBlockTime + xHalfMaxBlockTime + xStartMargin );

            if( xTaskAbortDelay( xBlockingTask ) != pdPASS )
            {
                xErrorOccurred = __LINE__;
            }

            /* Reset the priority to the normal controlling priority. */
            vTaskPrioritySet( NULL, abtCONTROLLING_PRIORITY );

            /* Now wait to be notified that the secondary task has completed its
             * test. */
            ulTaskNotifyTake( pdTRUE, portMAX_DELAY );

            /* Did the entire test run for the expected time, which is two full
             * block times plus the half block time caused by calling
             * xTaskAbortDelay()? */
            prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, ( xMaxBlockTime + xMaxBlockTime + xHalfMaxBlockTime ) );

            /* Move onto the next test. */
            ulTestToPerform++;

            if( ulTestToPerform >= abtMAX_TESTS )
            {
                ulTestToPerform = 0;
            }

            /* To indicate this task is still executing. */
            xControllingCycles++;
        }
    }
/*-----------------------------------------------------------*/

    static void prvBlockingTask( void * pvParameters )
    {
        TaskHandle_t xControllingTask;
        uint32_t ulNotificationValue;
        const uint32_t ulMax = 0xffffffffUL;

        /* Just to remove compiler warnings. */
        ( void ) pvParameters;

        /* Start by performing a few tests to cover code not exercised in the loops
         * below. */
        prvPerformSingleTaskTests();

        xControllingTask = xTaskGetHandle( pcControllingTaskName );
        configASSERT( xControllingTask );

        for( ; ; )
        {
            /* Wait to be notified of the test that is to be performed next. */
            xTaskNotifyWait( 0, ulMax, &ulNotificationValue, portMAX_DELAY );

            switch( ulNotificationValue )
            {
                case abtNOTIFY_WAIT_ABORTS:
                    prvTestAbortingTaskNotifyWait();
                    break;

                case abtNOTIFY_TAKE_ABORTS:
                    prvTestAbortingTaskNotifyTake();
                    break;

                case abtDELAY_ABORTS:
                    prvTestAbortingTaskDelay();
                    break;

                case abtDELAY_UNTIL_ABORTS:
                    prvTestAbortingTaskDelayUntil();
                    break;

                case abtSEMAPHORE_TAKE_ABORTS:
                    prvTestAbortingSemaphoreTake();
                    break;

                case abtEVENT_GROUP_ABORTS:
                    prvTestAbortingEventGroupWait();
                    break;

                case abtQUEUE_SEND_ABORTS:
                    prvTestAbortingQueueSend();
                    break;

                case abtSTREAM_BUFFER_RECEIVE:
                    prvTestAbortingStreamBufferReceive();
                    break;

                default:
                    /* Should not get here. */
                    break;
            }

            /* Let the primary task know the test is complete. */
            xTaskNotifyGive( xControllingTask );

            /* To indicate this task is still executing. */
            xBlockingCycles++;
        }
    }
/*-----------------------------------------------------------*/

    static void prvPerformSingleTaskTests( void )
    {
        TaskHandle_t xThisTask;
        BaseType_t xReturned;

        /* Try unblocking this task using both the task and ISR versions of the API -
         * both should return false as this task is not blocked. */
        xThisTask = xTaskGetCurrentTaskHandle();

        xReturned = xTaskAbortDelay( xThisTask );

        if( xReturned != pdFALSE )
        {
            xErrorOccurred = __LINE__;
        }
    }
/*-----------------------------------------------------------*/

    static void prvTestAbortingTaskDelayUntil( void )
    {
        TickType_t xTimeAtStart, xLastBlockTime;
        BaseType_t xReturned;

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* Take a copy of the time as it is updated in the call to
         * xTaskDelayUntil() but its original value is needed to determine the actual
         * time spend in the Blocked state. */
        xLastBlockTime = xTimeAtStart;

        /* This first delay should just time out. */
        xReturned = xTaskDelayUntil( &xLastBlockTime, xMaxBlockTime );
        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );
        configASSERT( xReturned == pdTRUE );

        /* Remove compiler warning about value being set but not used in the case
         * configASSERT() is not defined. */
        ( void ) xReturned;

        /* This second delay should be aborted by the primary task half way
         * through.  Again take a copy of the time as it is updated in the call to
         * vTaskDelayUntil() buts its original value is needed to determine the amount
         * of time actually spent in the Blocked state.  This uses vTaskDelayUntil()
         * in place of xTaskDelayUntil() for test coverage. */
        xTimeAtStart = xTaskGetTickCount();
        xLastBlockTime = xTimeAtStart;
        vTaskDelayUntil( &xLastBlockTime, xMaxBlockTime );
        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xHalfMaxBlockTime );

        /* As with the other tests, the third block period should not time out. */
        xTimeAtStart = xTaskGetTickCount();
        xLastBlockTime = xTimeAtStart;
        xReturned = xTaskDelayUntil( &xLastBlockTime, xMaxBlockTime );
        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );
        configASSERT( xReturned == pdTRUE );

        /* Remove compiler warning about value being set but not used in the case
         * configASSERT() is not defined. */
        ( void ) xReturned;
    }
/*-----------------------------------------------------------*/

    static void prvTestAbortingTaskDelay( void )
    {
        TickType_t xTimeAtStart;

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This first delay should just time out. */
        vTaskDelay( xMaxBlockTime );
        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This second delay should be aborted by the primary task half way
         * through. */
        vTaskDelay( xMaxBlockTime );
        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xHalfMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This third delay should just time out again. */
        vTaskDelay( xMaxBlockTime );
        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );
    }
/*-----------------------------------------------------------*/

    static void prvTestAbortingTaskNotifyTake( void )
    {
        TickType_t xTimeAtStart;
        uint32_t ulReturn;

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This first delay should just time out. */
        ulReturn = ulTaskNotifyTake( pdFALSE, xMaxBlockTime );

        if( ulReturn != 0 )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This second delay should be aborted by the primary task half way
         * through. */
        ulReturn = ulTaskNotifyTake( pdFALSE, xMaxBlockTime );

        if( ulReturn != 0 )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xHalfMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This third delay should just time out again. */
        ulReturn = ulTaskNotifyTake( pdFALSE, xMaxBlockTime );

        if( ulReturn != 0 )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );
    }
/*-----------------------------------------------------------*/

    static void prvTestAbortingEventGroupWait( void )
    {
        TickType_t xTimeAtStart;
        EventGroupHandle_t xEventGroup;
        EventBits_t xBitsToWaitFor = ( EventBits_t ) 0x01, xReturn;

        #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
        {
            static StaticEventGroup_t xEventGroupBuffer;

            /* Create the event group.  Statically allocated memory is used so the
             * creation cannot fail. */
            xEventGroup = xEventGroupCreateStatic( &xEventGroupBuffer );
        }
        #else
        {
            xEventGroup = xEventGroupCreate();
            configASSERT( xEventGroup );
        }
        #endif /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This first delay should just time out. */
        xReturn = xEventGroupWaitBits( xEventGroup, xBitsToWaitFor, pdTRUE, pdTRUE, xMaxBlockTime );

        if( xReturn != 0x00 )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This second delay should be aborted by the primary task half way
         * through. */
        xReturn = xEventGroupWaitBits( xEventGroup, xBitsToWaitFor, pdTRUE, pdTRUE, xMaxBlockTime );

        if( xReturn != 0x00 )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xHalfMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This third delay should just time out again. */
        xReturn = xEventGroupWaitBits( xEventGroup, xBitsToWaitFor, pdTRUE, pdTRUE, xMaxBlockTime );

        if( xReturn != 0x00 )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );

        /* Not really necessary in this case, but for completeness. */
        vEventGroupDelete( xEventGroup );
    }
/*-----------------------------------------------------------*/

    static void prvTestAbortingStreamBufferReceive( void )
    {
        TickType_t xTimeAtStart;
        StreamBufferHandle_t xStreamBuffer;
        size_t xReturn;
        const size_t xTriggerLevelBytes = ( size_t ) 1;
        uint8_t uxRxData;

        #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
        {
            /* Defines the memory that will actually hold the streams within the
             * stream buffer. */
            static uint8_t ucStorageBuffer[ sizeof( configMESSAGE_BUFFER_LENGTH_TYPE ) + 1 ];

            /* The variable used to hold the stream buffer structure. */
            StaticStreamBuffer_t xStreamBufferStruct;


            xStreamBuffer = xStreamBufferCreateStatic( sizeof( ucStorageBuffer ),
                                                       xTriggerLevelBytes,
                                                       ucStorageBuffer,
                                                       &xStreamBufferStruct );
        }
        #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */
        {
            xStreamBuffer = xStreamBufferCreate( sizeof( uint8_t ), xTriggerLevelBytes );
            configASSERT( xStreamBuffer );
        }
        #endif /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This first delay should just time out. */
        xReturn = xStreamBufferReceive( xStreamBuffer, &uxRxData, sizeof( uxRxData ), xMaxBlockTime );

        if( xReturn != 0x00 )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This second delay should be aborted by the primary task half way
         * through xMaxBlockTime. */
        xReturn = xStreamBufferReceive( xStreamBuffer, &uxRxData, sizeof( uxRxData ), xMaxBlockTime );

        if( xReturn != 0x00 )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xHalfMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This third delay should just time out again. */
        xReturn = xStreamBufferReceive( xStreamBuffer, &uxRxData, sizeof( uxRxData ), xMaxBlockTime );

        if( xReturn != 0x00 )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );

        /* Not really necessary in this case, but for completeness. */
        vStreamBufferDelete( xStreamBuffer );
    }
/*-----------------------------------------------------------*/

    static void prvTestAbortingQueueSend( void )
    {
        TickType_t xTimeAtStart;
        BaseType_t xReturn;
        const UBaseType_t xQueueLength = ( UBaseType_t ) 1;
        QueueHandle_t xQueue;
        uint8_t ucItemToQueue;

        #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
        {
            static StaticQueue_t xQueueBuffer;
            static uint8_t ucQueueStorage[ sizeof( uint8_t ) ];

            /* Create the queue.  Statically allocated memory is used so the
             * creation cannot fail. */
            xQueue = xQueueCreateStatic( xQueueLength, sizeof( uint8_t ), ucQueueStorage, &xQueueBuffer );
        }
        #else
        {
            xQueue = xQueueCreate( xQueueLength, sizeof( uint8_t ) );
            configASSERT( xQueue );
        }
        #endif /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */

        /* This function tests aborting when in the blocked state waiting to send,
         * so the queue must be full.  There is only one space in the queue. */
        xReturn = xQueueSend( xQueue, &ucItemToQueue, xMaxBlockTime );

        if( xReturn != pdPASS )
        {
            xErrorOccurred = __LINE__;
        }

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This first delay should just time out. */
        xReturn = xQueueSend( xQueue, &ucItemToQueue, xMaxBlockTime );

        if( xReturn != pdFALSE )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This second delay should be aborted by the primary task half way
         * through. */
        xReturn = xQueueSend( xQueue, &ucItemToQueue, xMaxBlockTime );

        if( xReturn != pdFALSE )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xHalfMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This third delay should just time out again. */
        xReturn = xQueueSend( xQueue, &ucItemToQueue, xMaxBlockTime );

        if( xReturn != pdFALSE )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );

        /* Not really necessary in this case, but for completeness. */
        vQueueDelete( xQueue );
    }
/*-----------------------------------------------------------*/

    static void prvTestAbortingSemaphoreTake( void )
    {
        TickType_t xTimeAtStart;
        BaseType_t xReturn;
        SemaphoreHandle_t xSemaphore;

        #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
        {
            static StaticSemaphore_t xSemaphoreBuffer;

            /* Create the semaphore.  Statically allocated memory is used so the
             * creation cannot fail. */
            xSemaphore = xSemaphoreCreateBinaryStatic( &xSemaphoreBuffer );
        }
        #else
        {
            xSemaphore = xSemaphoreCreateBinary();
        }
        #endif

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This first delay should just time out. */
        xReturn = xSemaphoreTake( xSemaphore, xMaxBlockTime );

        if( xReturn != pdFALSE )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This second delay should be aborted by the primary task half way
         * through xMaxBlockTime. */
        xReturn = xSemaphoreTake( xSemaphore, portMAX_DELAY );

        if( xReturn != pdFALSE )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xHalfMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This third delay should just time out again. */
        xReturn = xSemaphoreTake( xSemaphore, xMaxBlockTime );

        if( xReturn != pdFALSE )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );

        /* Not really necessary in this case, but for completeness. */
        vSemaphoreDelete( xSemaphore );
    }
/*-----------------------------------------------------------*/

    static void prvTestAbortingTaskNotifyWait( void )
    {
        TickType_t xTimeAtStart;
        BaseType_t xReturn;

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This first delay should just time out. */
        xReturn = xTaskNotifyWait( 0, 0, NULL, xMaxBlockTime );

        if( xReturn != pdFALSE )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This second delay should be aborted by the primary task half way
         * through xMaxBlockTime. */
        xReturn = xTaskNotifyWait( 0, 0, NULL, portMAX_DELAY );

        if( xReturn != pdFALSE )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xHalfMaxBlockTime );

        /* Note the time before the delay so the length of the delay is known. */
        xTimeAtStart = xTaskGetTickCount();

        /* This third delay should just time out again. */
        xReturn = xTaskNotifyWait( 0, 0, NULL, xMaxBlockTime );

        if( xReturn != pdFALSE )
        {
            xErrorOccurred = __LINE__;
        }

        prvCheckExpectedTimeIsWithinAnAcceptableMargin( xTimeAtStart, xMaxBlockTime );
    }
/*-----------------------------------------------------------*/

    static void prvCheckExpectedTimeIsWithinAnAcceptableMargin( TickType_t xStartTime,
                                                                TickType_t xExpectedBlockTime )
    {
        TickType_t xTimeNow, xActualBlockTime;

        xTimeNow = xTaskGetTickCount();
        xActualBlockTime = xTimeNow - xStartTime;

        /* The actual block time should not be less than the expected block time. */
        if( xActualBlockTime < xExpectedBlockTime )
        {
            xErrorOccurred = __LINE__;
        }

        /* The actual block time can be greater than the expected block time, as it
         * depends on the priority of the other tasks, but it should be within an
         * acceptable margin. */
        if( xActualBlockTime > ( xExpectedBlockTime + xAllowableMargin ) )
        {
            xErrorOccurred = __LINE__;
        }
    }
/*-----------------------------------------------------------*/

    BaseType_t xAreAbortDelayTestTasksStillRunning( void )
    {
        static BaseType_t xLastControllingCycleCount = 0, xLastBlockingCycleCount = 0;
        BaseType_t xReturn = pdPASS;

        /* Have both tasks performed at least one cycle since this function was
         * last called? */
        if( xControllingCycles == xLastControllingCycleCount )
        {
            xReturn = pdFAIL;
        }

        if( xBlockingCycles == xLastBlockingCycleCount )
        {
            xReturn = pdFAIL;
        }

        if( xErrorOccurred != pdFALSE )
        {
            xReturn = pdFAIL;
        }

        xLastBlockingCycleCount = xBlockingCycles;
        xLastControllingCycleCount = xControllingCycles;

        return xReturn;
    }

#endif /* INCLUDE_xTaskAbortDelay == 1 */
