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 should correctly increment the test data and signal
20 * completion.
21 * - The consumer should correctly wait for data update, consume it, and signal
22 * completion.
23 * - Data integrity should be maintained between producer and consumer.
24 * - Synchronization is properly done so both can see consistent data.
25 * - Random latency is introduced to simulate racing scenarios.
26 */
27
28 #define TEST_LATENCY_TICK (1)
29 #define TEST_LOOP_TIMES (60 * RT_TICK_PER_SECOND)
30 #define TEST_PROGRESS_ON (RT_TICK_PER_SECOND)
31
32 #include "utest.h"
33
34 #include <ipc/completion.h>
35 #include <rtthread.h>
36 #include <stdlib.h>
37
38 static struct rt_completion _prod_completion;
39 static struct rt_completion _cons_completion;
40 static int _test_data = 0;
41 static rt_atomic_t _progress_counter;
42 static struct rt_semaphore _thr_exit_sem;
43
done_safely(struct rt_completion * completion)44 static void done_safely(struct rt_completion *completion)
45 {
46 rt_err_t error;
47
48 /* Signal completion */
49 error = rt_completion_wakeup(completion);
50
51 /* try again if failed to produce */
52 if (error == -RT_EEMPTY)
53 {
54 rt_thread_yield();
55 }
56 else if (error)
57 {
58 uassert_false(0);
59 rt_thread_delete(rt_thread_self());
60 }
61 }
62
wait_safely(struct rt_completion * completion)63 static void wait_safely(struct rt_completion *completion)
64 {
65 rt_err_t error;
66 do
67 {
68 error = rt_completion_wait_flags(completion, RT_WAITING_FOREVER,
69 RT_INTERRUPTIBLE);
70 if (error)
71 {
72 uassert_true(error == -RT_EINTR);
73 rt_thread_yield();
74 }
75 else
76 {
77 break;
78 }
79 } while (1);
80 }
81
producer_thread_entry(void * parameter)82 static void producer_thread_entry(void *parameter)
83 {
84 for (size_t i = 0; i < TEST_LOOP_TIMES; i++)
85 {
86 /* Produce data */
87 _test_data++;
88
89 /* notify consumer */
90 done_safely(&_prod_completion);
91
92 /* Delay before producing next data */
93 rt_thread_delay(TEST_LATENCY_TICK);
94
95 /* sync with consumer */
96 wait_safely(&_cons_completion);
97 }
98
99 rt_sem_release(&_thr_exit_sem);
100 }
101
_wait_until_edge(void)102 static void _wait_until_edge(void)
103 {
104 rt_tick_t entry_level, current;
105 rt_base_t random_latency;
106
107 entry_level = rt_tick_get();
108 do
109 {
110 current = rt_tick_get();
111 } while (current == entry_level);
112
113 /* give a random latency for test */
114 random_latency = rand();
115 entry_level = current;
116 for (size_t i = 0; i < random_latency; i++)
117 {
118 current = rt_tick_get();
119 if (current != entry_level) break;
120 }
121 }
122
consumer_thread_entry(void * parameter)123 static void consumer_thread_entry(void *parameter)
124 {
125 int local_test_data = 0;
126
127 rt_thread_startup(parameter);
128
129 for (size_t i = 0; i < TEST_LOOP_TIMES; i++)
130 {
131 /* add more random case for test */
132 _wait_until_edge();
133
134 /* Wait for data update */
135 wait_safely(&_prod_completion);
136
137 /* Consume data */
138 if (local_test_data + 1 != _test_data)
139 {
140 LOG_I("local test data is %d, shared test data is %d",
141 local_test_data, _test_data);
142 uassert_true(0);
143 }
144 else if (rt_atomic_add(&_progress_counter, 1) % TEST_PROGRESS_ON == 0)
145 {
146 uassert_true(1);
147 }
148
149 local_test_data = _test_data;
150 done_safely(&_cons_completion);
151 }
152
153 rt_sem_release(&_thr_exit_sem);
154 }
155
testcase(void)156 static void testcase(void)
157 {
158 /* Initialize completion object */
159 rt_completion_init(&_prod_completion);
160 rt_completion_init(&_cons_completion);
161
162 /* Create producer and consumer threads */
163 rt_thread_t producer_thread =
164 rt_thread_create("producer", producer_thread_entry, RT_NULL,
165 UTEST_THR_STACK_SIZE, UTEST_THR_PRIORITY, 100);
166 rt_thread_t consumer_thread =
167 rt_thread_create("consumer", consumer_thread_entry, producer_thread,
168 UTEST_THR_STACK_SIZE, UTEST_THR_PRIORITY, 100);
169 uassert_true(producer_thread != RT_NULL);
170 uassert_true(consumer_thread != RT_NULL);
171
172 LOG_I("Summary:\n"
173 "\tTest times: %ds(%d)",
174 TEST_LOOP_TIMES / RT_TICK_PER_SECOND, TEST_LOOP_TIMES);
175
176 rt_thread_startup(consumer_thread);
177
178 for (size_t i = 0; i < 2; i++)
179 {
180 rt_sem_take(&_thr_exit_sem, RT_WAITING_FOREVER);
181 }
182 }
183
utest_tc_init(void)184 static rt_err_t utest_tc_init(void)
185 {
186 _test_data = 0;
187 _progress_counter = 0;
188 rt_sem_init(&_thr_exit_sem, "test", 0, RT_IPC_FLAG_PRIO);
189 return RT_EOK;
190 }
191
utest_tc_cleanup(void)192 static rt_err_t utest_tc_cleanup(void)
193 {
194 rt_sem_detach(&_thr_exit_sem);
195 return RT_EOK;
196 }
197
198 UTEST_TC_EXPORT(testcase, "testcases.drivers.ipc.rt_completion.basic",
199 utest_tc_init, utest_tc_cleanup, 10);
200