笔者上周肝了几天,各方偷取,提炼,封装了点代码,由于UT需要支持跨平台运行,故对write函数作了一点封装,并且UT当中采用mock形式
int System::system_write(int fd, const void* buf, size_t count)
{
const int ret(::write(fd, buf, count));
ssize_t ret;
do
{
ret = ::write(fd, buf, count);
} while ((ret == -1) && (errno == EINTR));
if(ret == -1)
{
throw InternalSystemError("write fail.");
}
return ret;
}
信心满满的的case如下
TEST_F(ClientProxyTest, sendCanMsg_OK)
{
can_frame frame;
initSemSignal(frame, static_cast<INT16U>(EVENT_NAME::SEMINIT), 2, 0);
int msgsize_1 = 0;
int temp_socketfd_ = 0;
// mock hits in the destructor of eventfd,
// and context in sendCanMsg_OK is all invalid that time, so we use global value here
int gtest_temp_eventfd_ = 0;
int gtest_msgsize_2_ = 0;
EXPECT_CALL(System_, system_write(_,_, _))
.Times(2)
.WillOnce(::testing::DoAll(::testing::SaveArg<0>(&temp_socketfd_), ::testing::SaveArg<2>(&msgsize_1), Return(sizeof(can_frame))))
.WillRepeatedly(::testing::DoAll(::testing::SaveArg<0>(>est_temp_eventfd_), ::testing::SaveArg<2>(>est_msgsize_2_), Return(sizeof(can_frame))));
clientProxy_->sendCanMsg(frame);
EXPECT_EQ(msgsize_1, sizeof(can_frame));
EXPECT_EQ(temp_socketfd_, 100);
}
在VC2022环境下运行起来没啥问题(VC打开sanitizer功能后有问题,要求所有依赖的库都打开该功能,只能用于本地测试,而且还存在全局变量和mock本身的全局变量释放顺序非预期后,crash的问题,故平时不会打开了)。
linux(x86)环境,前段时间将sanitizer集成进去,结果运行后发现有非法地址访问,sanitizer给的信息不太一下子能够明白,最后gdb跟踪才大致有点发现,下班跑路后,在公交车上回放了一下,才恍然有误。
UT的本意就是预期write会调用两次,然而第二次调用是在clientProxy_析构流程中,笔者在写case的时候没有特别在意这一点。
走到析构流程,上述本地变量,由于已经退出该上下文,所以存储的地址无效。
int gtest_temp_eventfd_ = 0;
int gtest_msgsize_2_ = 0;
为了证实该分析,将这两个变量放到全局存储区,结果pass。
教训有点深刻的,类似问题的表现往往更明显,比如lambda捕获局部栈上变量的引用时,也会出现几乎同类型问题,开发时一般都会注意,写UT的时候反而不会有这种sense。