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