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