《Shadow of a Doubt:Testing for Divergences Between Software Versions 》

题目:Shadow of a Doubt:Testing for Divergences Between Software Versions
作者:Hristina Palikareva, Tomasz Kuchta,Cristian Cadar
单位:Department of Computing, Imperial College London, UK
出版:2016 IEEE/ACM 38th IEEE International Conference on Software Engineering

问题:用于覆盖patch的测试用例经常会测试新旧版本程序相同行为的部分,本文提出的方法用于产生反映patch引入的新行为的测试用例。
现在的很多方法是利用动态符号执行,但是只是达到全语句覆盖或者就算全分支覆盖对patch来说都是不够的,并没有测试到patch引入的所有新行为。
本文提出基于动态符号执行的技术,产生覆盖patch引入的新行为的测试输入。
方案:在一个符号执行实例中同时执行新旧两个版本程序,旧程序尾随新程序。遇到新旧程序不同的分支时产生测试这个不同之处的测试用例综合地测试新版本的新行为。想要(1)精确确定两个版本的不同行为 (2)保持低执行的时间和内存开销
主要贡献
1.影子符号执行技术,裁剪掉大量不相关的执行路径并减小程序探索空间。
2.统一两版本程序的方法,将其表示成单个被注释过的程序,使得我们能够在一个符号执行实例中执行两个版本程序。
3.实现了工具SHADOW。
原理
1.前提假设:拿到了一个能到达patch的测试输入。可通过KATCH工具拿到。
2.为每条路径维护当前程序位置,一个符号化存储(symbolic store )还有路径约束条件(pc)。符号执行期间有一些隐含的检查,如数组访问越界检查。
3.两个程序的行为不同有很多可能的定义,特别是能够获得更高层的语义信息时,本文采用代码级的通用定义:两个程序在特定输入下的行为由在执行期间遍历的程序控制流图边序列表示。代码级行为不同可能也可能不会导致输出结果不同。
4.开始时执行那个已有的能够到达patch的测试输入,并收集约束,到patch之前两个版本的符号化存储和路径约束条件应该是一样的,而之后两个版本可能就要不同地更新各自的符号化存储和路径约束。本文方法中按需更新,尽可能地共享存储。
5.遇到分支4路fork。两个行为相同(new&&old和!new&&!old),两个行为不同(new&&!old和!new&&old)。
6. 最初的输入遇到分支时有两种情况:
(1)最初的输入触发到了不同行为,但不够充分,于是在这个点我们在新版本上做一个有界限的符号执行。比如开始在一个限定时间内做广度优先搜索的符号执行。
(2)最初的输入执行的还是相同的,但还是有可能在这个分支处产生不同的。也就是说这个输入在两个程序中选择的是同一边的分支,但至少有一条行为不同的路径是可达的,我们就会探索这样的路径。对每个行为不同的路径,首先为他们产生测试输入反映不同行为,然后在新版本上继续有界限的符号执行。
7.不同行为分四种情况:
(1)只有新版本存在错误,这种就是回归bug需要被修复。
(2)只在旧版本存在错误,这是期望的不同行为,表示bug被修复了。
(3)能够传播到输出的不同,可以让开发者快速判断是希望的改变还是回归错误。
(4)没有导致明显区别的不同,可以把相关的输入加入到应用测试集中。
8.添加注释的过程是手动的,但相信有些模式可以自动化实现留到未来工作中。
9.添加注释的类型:
(1)修改一个右值表达式。change()注释尽可能深地处于表达式中,有利于优化。在条件语句中增加或删除布尔表达式可以:修改A为A||B,可以注释为if(A || change(false,B))。修改A为A&&B,可以注释为if(A && change(true,B))。从A||B或A&&B修改为B的情况就直接change(A||B,B)或change(A&&B,B)就可以了。
(2)增加或删除额外的赋值语句或条件语句。通过增加无用语句进行处理。
增加赋值语句x=E,可注释为x=change(x,E),删除则相反
增加语句if(C)…code…,可注释为if(change(false,C))…code…
删除在C条件下的语句,可注释为if(change(C,false))…code…
删除一条直接执行的语句:if(change(true,false))…code…
增加一条直接执行的语句:if(change(false,true))…code…
增加或删除变量声明就保留变量声明不用注释。
修改变量声明我们保留较大的类型。(考虑到算数溢出问题不知是否安全,但测试中很少遇到)
10.为了尽可能地在两版本间共享表达式,在遇到change()注释时不分别创建和维护符号化表达式,而是创建一个shadow expression。一个shadow expression包含两个子表达式,一个与旧版本关联,一个与新版本关联。这样可以使之后用到该表达式的地方不需要分别指向两个符号化表达式,只指向一个shadow expression即可,达到尽可能共享的目的。(当在当前路径条件下,一个shadow expression的两个子表达式等价,即不会引入语义上的不同行为,就不创建这个shadow expression了)
11.跨版本检查:对比(1)输出,包括出口代码(2)常见错误,特别是崩溃和不会出发崩溃的内存错误(by compiling the code with address sanitization)。当两版本程序输出结果不同时,要确定是期望的,还是回归bug,可以通过阅读每个patch的提交信息,里面有描述patch的目的。还有一些无法立即判断行为上的改变是否是期望的,这需要开发人员予以关注,这可能导致bug或缺少合适的文档。
实现
实现工具SHADOW,在KLEE之上实现的,利用了ZESTI扩展的混合执行功能。基于KLEE 02fa9e4d,LLVM 2.9,STP 1668版本。
得到初始输入:用gcov编译新版本程序并运行回归测试集。
混合执行阶段:用一个封装的脚本替换被测程序,把原始调用参数传给SHADOW。一个测试用例可能会多次调用一个给定的程序,为了测试第n次调用,脚本会先执行前n-1次调用,然后把第n次转给SHADOW。
BSE阶段(实现和混合阶段一样):把每个不同的点都存在队列中,然后在每个不同的点广度优先执行有界符号执行。在BSE期间为每条探索的路径产生一个输入,因为这些路径都是源自产生不同行为的点,所以这些生成的输入都可以反映出不同。
界限:混合执行阶段和BSE阶段,单次调用执行最多600s。实际符号执行阶段给570s,平分给队列中的所有不同点。每个阶段,设定执行一个完整的测试用例的全局时长最大3600s,执行所有到达patch的测试用例全局时长7200s。
重跑生成的测试用例:提供replay功能。使用一个封装脚本,调用原始版本的应用程序,用生成的用例替换原始参数。每个用例跑两次,一次旧的一次新的。比对输出和出口代码。重跑的时限为每个调用5s,一个测试用例60s,全局7200s。
局限
1.手动添加注释。
2. 有些patch需要更准确的环境建模。(受限于KLEE的模型不完整)
3.未来拟引入聚集和排序技术辅助开发者筛选发现的不同行为。
测试集和注释available: http://srg.doc.ic.ac.uk/projects/shadow

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值