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