为了行文的简单,RT_Thread将简称为RTT。
RTT的finsh组件,提供了非常强大的调试功能,我们可以将函数输出到finsh上,然后就可以在finsh中手动的调用这个函数,这一点非常的强大。可以说finsh是RTT的调试利器,善用finsh,可以极大的提高开发的效率。
关于finsh的详细的介绍,可以参考《RT_Thread实时操作系统编程指南》的 Finsh Shell系统一章,这里先只是简单的举一个例子。
在application.c中的最后添加如下代码
int testfunc(void)
{
rt_kprintf("hello, rt-thread!\n");
}
#include
FINSH_FUNCTION_EXPORT(testfunc, just a test function);
不要忘记在rtconfig.h中使能 RT_USING_FINSH宏.
我们编写了名为 testfunc的函数,然后希望在finsh中挑用它,那么我们需要使用FINSH_FUNCTION_EXPORT宏带两个参数,第一个参数是函数名,第二个参数是对函数的说明,可以是任意字符串,用户可以根据自己的喜好编写,不过建议对函数做客观的说明或介绍。
FINSH_FUNCTION_EXPORT 会函数testfunc输出给finsh,这样finsh就会“知道”有一个函数名为testfunc。
接下来,重新编译工程,启动串口工具,比如putty,windows系统自带的超级终端,或者SecureCRT。
复位开发板,你就可以看到如下信息:
\ | /
- RT - Thread Operating System
/ | \ 1.1.0 build Apr 15 2012
2006 - 2012 Copyright by rt-thread team
finsh />
此时,按下Tab键,可以看到如下语句
--function:
testfunc -- just a test function
list_mem -- list memory usage information
list_date -- list date
hello -- say hello world
version -- show RT-Thread version information
list_thread -- list thread
list_sem -- list semaphone in system
list_event -- list event in system
list_mutex -- list mutex in system
list_mailbox -- list mail box in system
list_msgqueue -- list message queue in system
list_mempool -- list memory pool in system
list_timer -- list timer in system
list_device -- list device in system
list -- list all symbol in system
--variable:
dummy -- dummy variable for finsh
finsh />
第一句就是我们刚刚编写的函数,左边是函数名,右边即函数说明。
接下来,在finsh上输入 t 然后按下Tab键,finsh会自动地为我们补全testfunc,然后我们手动输入 ()后回车,可以看到如下运行效果:
finsh />testfunc()
hello, rt-thread!
0, 0x00000000
finsh />
注:finsh跟linux下的shell很像,都具有tab键自动补全的功能,但是又有些不同,finsh的命令是C语言风格的,命令即函数调用语句。
可以看到,我们在finsh中成功的手动调用了刚刚编写的testfunc函数。这个特性在调试程序时非常有用。可见可以通过这种方式来编写一些测试程序,并通过finsh来手动的调用这些测试用例。
实际上,在正式的项目开发中,测试是非常重要的环节,是程序质量的重要保障。我们可能为此编写大量的测试用例,此时一个一个在finsh中手动的输入函数名来运行测试用例的方式显然后有些原始,这个工作没有任何技术含量,枯燥乏味且令人恼火,那么这种情况下,改怎么办呢?
别担心,RTT为我们提供了所谓的 测试用例(TestCase, 简称为TC) 框架。TC框架可以帮助我们依次,自动的调用多个测试用例函数。
下一节,我们将添加一个testfunc2函数,跟testfunc函数一起采用TC框架,首先准备两个测试用例代码(为了代码的清晰,我么将测试用例放在独立的c文件中).
在当前BSP主目录下,创建一个名为testcase的文件夹。
将下面代码保存为 testfunc1.c ,存放到testcase/下。
#include
int testfunc1(void)
{
rt_kprintf("hello, rt-thread! this is testfunc1\n");
}
#include
FINSH_FUNCTION_EXPORT(testfunc1, just a test function);
将下面代码保存为 testfunc2.c 存放到testcase/下。
#include
int testfunc2(void)
{
rt_kprintf("hello, rt-thread! this is testfunc2\n");
}
#include
FINSH_FUNCTION_EXPORT(testfunc2, just a test function);
使能如下两个宏
#define RT_USING_TC
#define RT_USING_FINSH
因为TC测试框架需要如下这两个文件,
tc_comm.c
tc_comm.h
这两个文件位于RTT源码包examples/kernel/目录下,此目录下出这两个文件以外,还包含其他很多文件,那些文件是RTT官方提供基于TC框架的测试用例,它们用于测试RTT内核的基本功能。
这里,笔者要忍不住吐槽两句,当前RTT的文件放置仍然有些混乱,tc_comm.c和tc_comm.h不应该与测试用例放在一起。我们为其他的组件编写的测试用例放在什么地方呢?
将上面这两个文件复制到当前BSP目录下的testcase目录下。
将下面代码 testcase/SConscript文件
from building import *
src = Glob("*.c")
group = DefineGroup('testcase', src, depend = ['RT_USING_TC'])
Return('group')
修改当前BSP目录下的SConstuct文件,添加如下代码
if GetDepend('RT_USING_TC'):
objs = objs + SConscript(GetCurrentDir() + '/testcase/SConscript', variant_dir='build/tc', duplicate=0)
将 testfunc1.c 修改为:
#include
#include "tc_comm.h"
int testfunc1(void)
{
rt_kprintf("hello, rt-thread! this is testfunc1\n");
}
#include
FINSH_FUNCTION_EXPORT(testfunc1, just a test function);
static void _tc_cleanup()
{
tc_done(TC_STAT_PASSED);
}
int _tc_testfunc1()
{
/* 设置TestCase清理回调函数 */
tc_cleanup(_tc_cleanup);
testfunc1();
/* 返回TestCase运行的最长时间 */
return 100;
}
/* 输出函数命令到finsh shell中 */
FINSH_FUNCTION_EXPORT(_tc_testfunc1, a dynamic thread example);
同样,将 testfunc2.c 修改为:
#include
#include "tc_comm.h"
int testfunc2(void)
{
rt_kprintf("hello, rt-thread! this is testfunc2\n");
}
#include
FINSH_FUNCTION_EXPORT(testfunc2, just a test function);
static void _tc_cleanup()
{
tc_done(TC_STAT_PASSED);
}
int _tc_testfunc2()
{
/* 设置TestCase清理回调函数 */
tc_cleanup(_tc_cleanup);
testfunc2();
/* 返回TestCase运行的最长时间 */
return 100;
}
/* 输出函数命令到finsh shell中 */
FINSH_FUNCTION_EXPORT(_tc_testfunc2, a dynamic thread example);
在命令行下执行scons 编译,然后启动finsh,按下tab键,可以看到 testfunc1和testfunc2.
除此之外,还多出来三个函数,分别是
在tc_comm.h中有这三个函数的原型,这三个函数就是TC框架提供给我们的函数,我们只需要执行 tc_start(“test”),TC框架就会自动的依次调用 testfunc1 和 testfunc2函数。效果如下所示。
finsh />tc_start("test")
805434376, 0x3001f408
finsh />Run TestCase: testfunc1
hello, rt-thread! this is testfunc1
TestCase[testfunc1] passed
Run TestCase: testfunc2
hello, rt-thread! this is testfunc2
TestCase[testfunc2] passed
RT-Thread TestCase Running Done!
finsh />
这里简要说明一下 tc_start(“test”)的含义,我们编写两个测试函数的函数名分别为
显然这两个函数具有共同的前缀字符串,这里我输入的是“test”, 当然你也可以输入 “testf”, “testfun”等等。
关于Finsh Shell的更多介绍请参考《RT_Thread实时操作系统编程指南》 的“Finsh Shell系统”章。
RTT的官方源码包中已经包含了一些测试用例,它们位于example/目录下。读者需要注意,只有example/kernel目录下的源码文件是基于TC的测试用例,其他子目录下的文件并不是基于TC框架框架编写的。
修改当前BSP目录下的SConstuct文件,将如下代码
if GetDepend('RT_USING_TC'):
objs = objs + SConscript(GetCurrentDir() + '/testcase/SConscript', variant_dir='build/tc', duplicate=0)
修改为
if GetDepend('RT_USING_TC'):
objs = objs + SConscript((RTT_ROOT + '/example/kernel/SConscript', variant_dir='build/tc/kernel', duplicate=0)
编译运行,启动finsh后,按下TAB键,可以出现很多函数, 这些函数就是输出到Finsh上的测试用例。
你可以执行
tc_start(“thread”) 对kernel做线程相关的测试
tc_start(“timer”) 对kernel做定时器相关的测试
tc_start(“semahore”) 对kernel做信号量相关的测试
...等等
下一节将剖析RTT的TestCase框架源码。
在example/kernel/下提供了一个tc_sample.c,这个是RTT官方提供的TC测试用例的样本代码。我们将研究此文件来分析TestCase框架。
TC框架所涉及到文件为
examples/kernel/tc_comm.c
examples/kernel/tc_comm.h
examples/kernel/tc_sample.c
其中tc_sample.c源码如下:
#include
#include "tc_comm.h"
static rt_thread_t tid = RT_NULL;
static void sample_thread(void* parameter)
{
rt_kprintf("I'm sample!\n");
}
static void sample_thread_cleanup(struct rt_thread *p)
{
tid = RT_NULL;
tc_done(TC_STAT_PASSED);
}
int sample_init()
{
tid = rt_thread_create("t",
sample_thread, RT_NULL,
THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
if (tid != RT_NULL)
{
rt_thread_startup(tid);
tid->cleanup = sample_thread_cleanup;
}
else
tc_stat(TC_STAT_END | TC_STAT_FAILED);
return 0;
}
#ifdef RT_USING_TC
static void _tc_cleanup()
{
/* lock scheduler */
rt_enter_critical();
/* delete thread */
if (tid != RT_NULL)
{
rt_kprintf("tid1 is bad\n");
tc_stat(TC_STAT_FAILED);
}
/* unlock scheduler */
rt_exit_critical();
}
int _tc_sample()
{
/* set tc cleanup */
tc_cleanup(_tc_cleanup);
sample_init();
return 25;
}
FINSH_FUNCTION_EXPORT(_tc_sample, a thread testcase example);
#else
int rt_application_init()
{
sample_init();
return 0;
}
#endif
未完待续....