1 /*
2  * Copyright (c) 2006-2024, RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2024-04-30     Shell        init ver.
9  */
10 
11 /**
12  * Test Case for rt_completion API
13  *
14  * The test simulates a producer-consumer interaction where a producer thread
15  * generates data, and a consumer thread consumes the data after waiting for its
16  * availability using rt_completion synchronization primitives.
17  *
18  * Test Criteria:
19  * - The producer produces data correctly and notifies the consumer thread.
20  * - The consumer receives data correctly and acknowledges receipt to the
21  *   producer.
22  * - The producer and consumer threads synchronize their operations effectively.
23  * - Verify the correctness of data production and consumption between producer
24  *   and consumer threads.
25  * - The asynchronous woken of consumer thread was handled properly so the
26  *   consumer don't lose woken from producer.
27  *
28  * Test APIs:
29  * - rt_completion_init()
30  * - rt_completion_wakeup()
31  * - rt_completion_wait_flags()
32  */
33 
34 #define TEST_LATENCY_TICK (1)
35 #define TEST_LOOP_TIMES   (60 * RT_TICK_PER_SECOND)
36 #define TEST_PROGRESS_ON  (RT_TICK_PER_SECOND)
37 
38 #include "utest.h"
39 
40 #include <ipc/completion.h>
41 #include <rtthread.h>
42 #include <stdlib.h>
43 
44 static struct rt_completion _prod_completion;
45 static struct rt_completion _cons_completion;
46 static int _test_data;
47 static int _async_intr_count;
48 static rt_atomic_t _progress_counter;
49 static struct rt_semaphore _thr_exit_sem;
50 
_test_thread_exit_failure(char * string)51 static void _test_thread_exit_failure(char *string)
52 {
53     LOG_E("\t[TEST failed] %s", string);
54 
55     rt_sem_release(&_thr_exit_sem);
56     rt_thread_delete(rt_thread_self());
57 }
58 
done_safely(struct rt_completion * completion)59 static void done_safely(struct rt_completion *completion)
60 {
61     rt_err_t error;
62 
63     /* Signal completion */
64     error = rt_completion_wakeup(completion);
65 
66     /* try again if failed to produce */
67     if (error == -RT_EEMPTY)
68     {
69         rt_thread_yield();
70     }
71     else if (error)
72     {
73         uassert_true(error == RT_EOK);
74         _test_thread_exit_failure("unexpected error");
75     }
76 }
77 
wait_safely(struct rt_completion * completion)78 static void wait_safely(struct rt_completion *completion)
79 {
80     int try_times = 3;
81     rt_err_t error;
82     do
83     {
84         /* wait for one tick, to add more random */
85         error = rt_completion_wait_flags(completion, 1, RT_INTERRUPTIBLE);
86         if (error)
87         {
88             if (error == -RT_ETIMEOUT || error == -RT_EINTR)
89             {
90                 _async_intr_count++;
91             }
92             else
93             {
94                 LOG_I("Async event %d\n", error);
95                 uassert_true(0);
96             }
97             rt_thread_yield();
98         }
99         else
100         {
101             break;
102         }
103     } while (try_times--);
104 
105     if (error != RT_EOK)
106     {
107         uassert_true(error == RT_EOK);
108         _test_thread_exit_failure("wait failed");
109     }
110 }
111 
producer_thread_entry(void * parameter)112 static void producer_thread_entry(void *parameter)
113 {
114     for (size_t i = 0; i < TEST_LOOP_TIMES; i++)
115     {
116         /* Produce data */
117         _test_data++;
118 
119         /* Delay before producing next data */
120         rt_thread_delay(TEST_LATENCY_TICK);
121 
122         /* notify consumer */
123         done_safely(&_prod_completion);
124 
125         /* sync with consumer */
126         wait_safely(&_cons_completion);
127     }
128 
129     rt_sem_release(&_thr_exit_sem);
130 }
131 
consumer_thread_entry(void * parameter)132 static void consumer_thread_entry(void *parameter)
133 {
134     int local_test_data = 0;
135 
136     rt_thread_startup(parameter);
137 
138     for (size_t i = 0; i < TEST_LOOP_TIMES; i++)
139     {
140         /* Wait for data update */
141         wait_safely(&_prod_completion);
142 
143         /* Consume data */
144         if (local_test_data + 1 != _test_data)
145         {
146             LOG_I("local test data is %d, shared test data is %d",
147                   local_test_data, _test_data);
148             uassert_true(0);
149         }
150         else if (rt_atomic_add(&_progress_counter, 1) % TEST_PROGRESS_ON == 0)
151         {
152             uassert_true(1);
153         }
154 
155         local_test_data = _test_data;
156         done_safely(&_cons_completion);
157     }
158 
159     rt_sem_release(&_thr_exit_sem);
160 }
161 
162 rt_thread_t _watching_thread1;
163 rt_thread_t _watching_thread2;
164 
testcase(void)165 static void testcase(void)
166 {
167     /* Initialize completion object */
168     rt_completion_init(&_prod_completion);
169     rt_completion_init(&_cons_completion);
170 
171     /* Create producer and consumer threads */
172     rt_thread_t producer_thread =
173         rt_thread_create("producer", producer_thread_entry, RT_NULL,
174                          UTEST_THR_STACK_SIZE, UTEST_THR_PRIORITY, 100);
175     rt_thread_t consumer_thread =
176         rt_thread_create("consumer", consumer_thread_entry, producer_thread,
177                          UTEST_THR_STACK_SIZE, UTEST_THR_PRIORITY, 100);
178     uassert_true(producer_thread != RT_NULL);
179     uassert_true(consumer_thread != RT_NULL);
180     _watching_thread1 = consumer_thread;
181     _watching_thread2 = producer_thread;
182 
183     rt_thread_startup(consumer_thread);
184 
185     for (size_t i = 0; i < 2; i++)
186     {
187         rt_sem_take(&_thr_exit_sem, RT_WAITING_FOREVER);
188     }
189 
190     LOG_I("Summary:\n"
191           "\tTest times: %ds(%d times)\n"
192           "\tAsync interruption count: %d\n",
193           TEST_LOOP_TIMES / RT_TICK_PER_SECOND, TEST_LOOP_TIMES,
194           _async_intr_count);
195 }
196 
utest_tc_init(void)197 static rt_err_t utest_tc_init(void)
198 {
199     _test_data = 0;
200     _progress_counter = 0;
201     _async_intr_count = 0;
202     rt_sem_init(&_thr_exit_sem, "test", 0, RT_IPC_FLAG_PRIO);
203     return RT_EOK;
204 }
205 
utest_tc_cleanup(void)206 static rt_err_t utest_tc_cleanup(void)
207 {
208     rt_sem_detach(&_thr_exit_sem);
209     return RT_EOK;
210 }
211 
212 UTEST_TC_EXPORT(testcase, "testcases.drivers.ipc.rt_completion.timeout",
213                 utest_tc_init, utest_tc_cleanup, 1000);
214