1 /*
2 * Copyright (c) 2006-2021, RT-Thread Development Team
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 *
6 * Change Logs:
7 * Date Author Notes
8 * 2018-11-19 MurphyZhao the first version
9 */
10
11 #include <rtthread.h>
12 #include <string.h>
13 #include <stdlib.h>
14 #include "utest.h"
15 #include "utest_log.h"
16
17 #define DBG_TAG "utest"
18 #ifdef UTEST_DEBUG
19 #define DBG_LVL DBG_LOG
20 #else
21 #define DBG_LVL DBG_INFO
22 #endif
23 #include <rtdbg.h>
24
25 #if RT_CONSOLEBUF_SIZE < 256
26 #error "RT_CONSOLEBUF_SIZE is less than 256!"
27 #endif
28
29 #ifdef UTEST_THR_STACK_SIZE
30 #define UTEST_THREAD_STACK_SIZE UTEST_THR_STACK_SIZE
31 #else
32 #define UTEST_THREAD_STACK_SIZE 4096
33 #endif
34
35 #ifdef UTEST_THR_PRIORITY
36 #define UTEST_THREAD_PRIORITY UTEST_THR_PRIORITY
37 #else
38 #define UTEST_THREAD_PRIORITY FINSH_THREAD_PRIORITY
39 #endif
40
41 static rt_uint8_t utest_log_lv = UTEST_LOG_ALL;
42 static utest_tc_export_t tc_table = RT_NULL;
43 static rt_size_t tc_num;
44 static rt_uint32_t tc_loop;
45 static rt_uint8_t *tc_fail_list;
46 static struct utest local_utest = {UTEST_PASSED, 0, 0};
47
48 #if defined(__ICCARM__) || defined(__ICCRX__) /* for IAR compiler */
49 #pragma section="UtestTcTab"
50 #elif defined(_MSC_VER)
51 #pragma section("UtestTcTab$a", read)
52 __declspec(allocate("UtestTcTab$a")) const struct utest_tc_export __tc_export_begin =
53 {
54 "__start",
55 };
56
57 #pragma section("UtestTcTab$z", read)
58 __declspec(allocate("UtestTcTab$z")) const struct utest_tc_export __tc_export_end =
59 {
60 "__end",
61 };
62 #endif
63
64 #define TC_FAIL_LIST_SIZE (RT_ALIGN(tc_num, 8) / 8)
65 #define TC_FAIL_LIST_MARK_FAILED(index) (tc_fail_list[index / 8] |= (1UL << (index % 8)))
66 #define TC_FAIL_LIST_IS_FAILED(index) (tc_fail_list[index / 8] & (1UL << (index % 8)))
67
utest_log_lv_set(rt_uint8_t lv)68 void utest_log_lv_set(rt_uint8_t lv)
69 {
70 if (lv == UTEST_LOG_ALL || lv == UTEST_LOG_ASSERT)
71 {
72 utest_log_lv = lv;
73 }
74 }
75
utest_init(void)76 int utest_init(void)
77 {
78 /* initialize the utest commands table.*/
79 #if defined(__ARMCC_VERSION) /* ARM C Compiler */
80 extern const int UtestTcTab$$Base;
81 extern const int UtestTcTab$$Limit;
82 tc_table = (utest_tc_export_t)&UtestTcTab$$Base;
83 tc_num = (utest_tc_export_t)&UtestTcTab$$Limit - tc_table;
84 #elif defined (__ICCARM__) || defined(__ICCRX__) /* for IAR Compiler */
85 tc_table = (utest_tc_export_t)__section_begin("UtestTcTab");
86 tc_num = (utest_tc_export_t)__section_end("UtestTcTab") - tc_table;
87 #else
88 unsigned int *ptr_begin, *ptr_end;
89 #if defined(__GNUC__)
90 extern const int __rt_utest_tc_tab_start;
91 extern const int __rt_utest_tc_tab_end;
92 ptr_begin = (unsigned int *)&__rt_utest_tc_tab_start;
93 ptr_end = (unsigned int *)&__rt_utest_tc_tab_end;
94 #elif defined(_MSC_VER)
95 ptr_begin = (unsigned int *)&__tc_export_begin;
96 ptr_end = (unsigned int *)&__tc_export_end;
97 ptr_begin += (sizeof(struct utest_tc_export) / sizeof(unsigned int));
98 #endif
99 while (*ptr_begin == 0) ptr_begin++;
100 ptr_end--;
101 while (*ptr_end == 0) ptr_end--;
102 /* copy tc_table from rodata section to ram */
103 for (unsigned int *ptr = ptr_begin; ptr < ptr_end;)
104 {
105 if (!tc_table)
106 tc_table = (utest_tc_export_t)rt_malloc(sizeof(struct utest_tc_export));
107 else
108 tc_table = (utest_tc_export_t)rt_realloc(tc_table, (tc_num + 1)* sizeof(struct utest_tc_export));
109 RT_ASSERT(tc_table);
110 tc_table[tc_num++] = *((utest_tc_export_t)ptr);
111 ptr += (sizeof(struct utest_tc_export) / sizeof(unsigned int));
112 while (*ptr == 0) ptr++;
113 }
114 #endif
115
116 LOG_I("utest is initialize success.");
117 LOG_I("total utest testcase num: (%d)", tc_num);
118 if (tc_num > 0)
119 {
120 tc_fail_list = rt_malloc(TC_FAIL_LIST_SIZE);
121 if(!tc_fail_list)
122 {
123 LOG_E("no memory, tc_fail_list init failed!");
124 }
125 }
126 return tc_num;
127 }
128 INIT_COMPONENT_EXPORT(utest_init);
129
utest_tc_list(void)130 static long utest_tc_list(void)
131 {
132 rt_size_t i = 0;
133
134 LOG_I("Commands list : ");
135
136 for (i = 0; i < tc_num; i++)
137 {
138 LOG_I("[testcase name]:%s; [run timeout]:%d", tc_table[i].name, tc_table[i].run_timeout);
139 }
140
141 return 0;
142 }
143 MSH_CMD_EXPORT_ALIAS(utest_tc_list, utest_list, output all utest testcase);
144
file_basename(const char * file)145 static const char *file_basename(const char *file)
146 {
147 char *end_ptr = RT_NULL;
148 char *rst = RT_NULL;
149
150 if (!((end_ptr = strrchr(file, '\\')) != RT_NULL || \
151 (end_ptr = strrchr(file, '/')) != RT_NULL) || \
152 (rt_strlen(file) < 2))
153 {
154 rst = (char *)file;
155 }
156 else
157 {
158 rst = (char *)(end_ptr + 1);
159 }
160 return (const char *)rst;
161 }
162
utest_help(void)163 static int utest_help(void)
164 {
165 rt_kprintf("\n");
166 rt_kprintf("Command: utest_run\n");
167 rt_kprintf(" info: Execute test cases.\n");
168 rt_kprintf(" format: utest_run [-thread or -help] [testcase name] [loop num]\n");
169 rt_kprintf(" usage:\n");
170 rt_kprintf(" 1. utest_run\n");
171 rt_kprintf(" Do not specify a test case name. Run all test cases.\n");
172 rt_kprintf(" 2. utest_run -thread\n");
173 rt_kprintf(" Do not specify a test case name. Run all test cases in threaded mode.\n");
174 rt_kprintf(" 3. utest_run testcaseA\n");
175 rt_kprintf(" Run 'testcaseA'.\n");
176 rt_kprintf(" 4. utest_run testcaseA 10\n");
177 rt_kprintf(" Run 'testcaseA' ten times.\n");
178 rt_kprintf(" 5. utest_run -thread testcaseA\n");
179 rt_kprintf(" Run 'testcaseA' in threaded mode.\n");
180 rt_kprintf(" 6. utest_run -thread testcaseA 10\n");
181 rt_kprintf(" Run 'testcaseA' ten times in threaded mode.\n");
182 rt_kprintf(" 7. utest_run test*\n");
183 rt_kprintf(" support '*' wildcard. Run all test cases starting with 'test'.\n");
184 rt_kprintf(" 8. utest_run -help\n");
185 rt_kprintf(" Show utest help information\n");
186 rt_kprintf("\n");
187 return 0;
188 }
189
utest_do_run(const char * utest_name)190 static void utest_do_run(const char *utest_name)
191 {
192 rt_size_t i;
193 rt_uint32_t index;
194 rt_bool_t is_find;
195 rt_uint32_t tc_fail_num = 0;
196 rt_uint32_t tc_run_num = 0;
197
198 for (index = 0; index < tc_loop; index ++)
199 {
200 i = 0;
201 is_find = RT_FALSE;
202
203 tc_fail_num = 0;
204 tc_run_num = 0;
205 if (tc_fail_list)
206 {
207 rt_memset(tc_fail_list, 0, TC_FAIL_LIST_SIZE);
208 }
209
210 LOG_I("[==========] [ utest ] loop %d/%d", index + 1, tc_loop);
211 LOG_I("[==========] [ utest ] started");
212 while(i < tc_num)
213 {
214 if (utest_name)
215 {
216 int len = rt_strlen(utest_name);
217 if (utest_name[len - 1] == '*')
218 {
219 len -= 1;
220 if (rt_memcmp(tc_table[i].name, utest_name, len) != 0)
221 {
222 i++;
223 continue;
224 }
225 }
226 else if (rt_strcmp(tc_table[i].name, utest_name) != 0)
227 {
228 i++;
229 continue;
230 }
231 }
232 is_find = RT_TRUE;
233
234 LOG_I("[----------] [ testcase ] (%s) started", tc_table[i].name);
235 if (tc_table[i].init != RT_NULL)
236 {
237 if (tc_table[i].init() != RT_EOK)
238 {
239 LOG_E("[ FAILED ] [ result ] testcase init (%s)", tc_table[i].name);
240 goto __tc_continue;
241 }
242 }
243
244 if (tc_table[i].tc != RT_NULL)
245 {
246 tc_table[i].tc();
247 if (local_utest.failed_num == 0)
248 {
249 LOG_I("[ PASSED ] [ result ] testcase (%s)", tc_table[i].name);
250 }
251 else
252 {
253 TC_FAIL_LIST_MARK_FAILED(i);
254 tc_fail_num ++;
255 LOG_E("[ FAILED ] [ result ] testcase (%s)", tc_table[i].name);
256 }
257 }
258 else
259 {
260 LOG_E("[ FAILED ] [ result ] testcase (%s)", tc_table[i].name);
261 }
262
263 if (tc_table[i].cleanup != RT_NULL)
264 {
265 if (tc_table[i].cleanup() != RT_EOK)
266 {
267 LOG_E("[ FAILED ] [ result ] testcase cleanup (%s)", tc_table[i].name);
268 goto __tc_continue;
269 }
270 }
271
272 __tc_continue:
273 LOG_I("[----------] [ testcase ] (%s) finished", tc_table[i].name);
274
275 tc_run_num ++;
276 i++;
277 }
278
279 if (i == tc_num && is_find == RT_FALSE && utest_name != RT_NULL)
280 {
281 LOG_I("[==========] [ utest ] Not find (%s)", utest_name);
282 LOG_I("[==========] [ utest ] finished");
283 break;
284 }
285
286 LOG_I("[==========] [ utest ] %d tests from %d testcase ran.", tc_run_num, tc_num);
287 LOG_I("[ PASSED ] [ result ] %d tests.", tc_run_num - tc_fail_num);
288
289 if(tc_fail_list && (tc_fail_num > 0))
290 {
291 LOG_E("[ FAILED ] [ result ] %d tests, listed below:", tc_fail_num);
292 for(i = 0; i < tc_num; i ++)
293 {
294 if (TC_FAIL_LIST_IS_FAILED(i))
295 {
296 LOG_E("[ FAILED ] [ result ] %s", tc_table[i].name);
297 }
298 }
299 }
300
301 LOG_I("[==========] [ utest ] finished");
302 }
303 }
304
utest_thr_entry(void * para)305 static void utest_thr_entry(void *para)
306 {
307 char *utest_name = (char *)para;
308 rt_thread_mdelay(1000); /* see commit:0dc7b9a for details */
309 rt_kprintf("\n");
310 utest_do_run(utest_name);
311 }
312
utest_thread_create(const char * utest_name)313 static void utest_thread_create(const char *utest_name)
314 {
315 rt_thread_t tid = RT_NULL;
316 tid = rt_thread_create("utest",
317 utest_thr_entry, (void *)utest_name,
318 UTEST_THREAD_STACK_SIZE, UTEST_THREAD_PRIORITY, 10);
319 if (tid != RT_NULL)
320 {
321 rt_thread_startup(tid);
322 }
323 }
324
325 #ifdef RT_UTEST_USING_AUTO_RUN
utest_auto_run(void)326 static int utest_auto_run(void)
327 {
328 tc_loop = 1;
329 utest_thread_create(RT_NULL);
330 return RT_EOK;
331 }
332 INIT_APP_EXPORT(utest_auto_run);
333 #endif /* RT_UTEST_USING_AUTO_RUN */
334
utest_testcase_run(int argc,char ** argv)335 int utest_testcase_run(int argc, char** argv)
336 {
337 static char utest_name[UTEST_NAME_MAX_LEN];
338 rt_memset(utest_name, 0x0, sizeof(utest_name));
339
340 tc_loop = 1;
341
342 if (argc == 1)
343 {
344 utest_thread_create(RT_NULL);
345 }
346 else if (argc == 2 || argc == 3 || argc == 4)
347 {
348 if (rt_strcmp(argv[1], "-thread") == 0)
349 {
350 if (argc == 3 || argc == 4)
351 {
352 rt_strncpy(utest_name, argv[2], sizeof(utest_name) -1);
353 if (argc == 4)
354 {
355 tc_loop = atoi(argv[3]);
356 }
357 }
358 utest_thread_create(utest_name);
359 }
360 else if (rt_strcmp(argv[1], "-help") == 0)
361 {
362 utest_help();
363 }
364 else
365 {
366 rt_strncpy(utest_name, argv[1], sizeof(utest_name) -1);
367 if (argc == 3)
368 {
369 tc_loop = atoi(argv[2]);
370 }
371 utest_do_run(utest_name);
372 }
373 }
374 else
375 {
376 LOG_E("[ error ] at (%s:%d), in param error.", __func__, __LINE__);
377 utest_help();
378 }
379
380 return RT_EOK;
381 }
382 MSH_CMD_EXPORT_ALIAS(utest_testcase_run, utest_run, utest_run [-thread or -help] [testcase name] [loop num]);
383
utest_handle_get(void)384 utest_t utest_handle_get(void)
385 {
386 return (utest_t)&local_utest;
387 }
388
utest_unit_run(test_unit_func func,const char * unit_func_name)389 void utest_unit_run(test_unit_func func, const char *unit_func_name)
390 {
391 LOG_I("[==========] utest unit name: (%s)", unit_func_name);
392 local_utest.error = UTEST_PASSED;
393 local_utest.passed_num = 0;
394 local_utest.failed_num = 0;
395
396 if (func != RT_NULL)
397 {
398 func();
399 }
400 }
401
402 /*
403 * utest_assert - assert function
404 *
405 * @param value - assert value
406 * @param file - file name
407 * @param line - line number
408 * @param func - function name
409 * @param msg - assert message
410 *
411 * @return - RT_TRUE: assert success; RT_FALSE: assert failed
412 */
utest_assert(int value,const char * file,int line,const char * func,const char * msg)413 rt_bool_t utest_assert(int value, const char *file, int line, const char *func, const char *msg)
414 {
415 rt_bool_t rst = RT_FALSE;
416
417 if (!(value))
418 {
419 local_utest.error = UTEST_FAILED;
420 local_utest.failed_num ++;
421 LOG_E("[ ASSERT ] [ unit ] at (%s); func: (%s:%d); msg: (%s)", file_basename(file), func, line, msg);
422 rst = RT_FALSE;
423 }
424 else
425 {
426 if (utest_log_lv == UTEST_LOG_ALL)
427 {
428 LOG_D("[ OK ] [ unit ] (%s:%d) is passed", func, line);
429 }
430 local_utest.error = UTEST_PASSED;
431 local_utest.passed_num ++;
432 rst = RT_TRUE;
433 }
434
435 return rst;
436 }
437
utest_assert_string(const char * a,const char * b,rt_bool_t equal,const char * file,int line,const char * func,const char * msg)438 void utest_assert_string(const char *a, const char *b, rt_bool_t equal, const char *file, int line, const char *func, const char *msg)
439 {
440 rt_bool_t rst = RT_FALSE;
441
442 if (a == RT_NULL || b == RT_NULL)
443 {
444 rst = utest_assert(0, file, line, func, msg);
445 }
446 else
447 {
448 if (equal)
449 {
450 if (rt_strcmp(a, b) == 0)
451 {
452 rst = utest_assert(1, file, line, func, msg);
453 }
454 else
455 {
456 rst = utest_assert(0, file, line, func, msg);
457 }
458 }
459 else
460 {
461 if (rt_strcmp(a, b) == 0)
462 {
463 rst = utest_assert(0, file, line, func, msg);
464 }
465 else
466 {
467 rst = utest_assert(1, file, line, func, msg);
468 }
469 }
470 }
471
472 if (!rst)
473 {
474 LOG_E("[ ASSERT ] [ unit ] str-a: (%s); str-b: (%s)", a, b);
475 }
476 }
477
utest_assert_buf(const char * a,const char * b,rt_size_t sz,rt_bool_t equal,const char * file,int line,const char * func,const char * msg)478 void utest_assert_buf(const char *a, const char *b, rt_size_t sz, rt_bool_t equal, const char *file, int line, const char *func, const char *msg)
479 {
480 if (a == RT_NULL || b == RT_NULL)
481 {
482 utest_assert(0, file, line, func, msg);
483 }
484
485 if (equal)
486 {
487 if (rt_memcmp(a, b, sz) == 0)
488 {
489 utest_assert(1, file, line, func, msg);
490 }
491 else
492 {
493 utest_assert(0, file, line, func, msg);
494 }
495 }
496 else
497 {
498 if (rt_memcmp(a, b, sz) == 0)
499 {
500 utest_assert(0, file, line, func, msg);
501 }
502 else
503 {
504 utest_assert(1, file, line, func, msg);
505 }
506 }
507 }
508