从精准化测试看ASM在Android中的强势插入-读懂diff

 

我们计算增量代码覆盖率的基础,就是要找出两个版本代码的差异,在Git环境下,我们可以很方便的通过Git脚本来获取这些数据。

Git获取diff信息

git diff命令可以使用如下格式,用来对比不同commit(或分支)间的增量代码:

git diff [<options>] <commit> <commit>

其中commit可以是分支名,也可以是commit的id,对比分支间的差异,可以简写为 git diff targetBranchName,表示对比当前分支与目标分支间的代码差异。

下面这张图,就是通过git diff指令获取的一段更新diff信息,如下所示。

git diff HEAD~1 HEAD

输出如下:

从精准化测试看ASM在Android中的强势插入-读懂diffimage-20210621155525706

diff指令,一定会有两个输入,即A和B,图中第一行,就标记了A和B文件。

对于版本A,它的符号是一个减号(「-」);而对于版本B ,它会使用一个加号(「+」)。

图中的第三四行,就是被标记的两个文件,针对这个标记,存在下面几种情况。

  • diff文件是新增文件,则---后面是/dev/null

  • diff文件被删除,则+++后面是/dev/null

  • diff发生修改,则---和+++后面都有文件名

通过这个,就可以区分文件状态。

Chunk Header

git diff的每个修改,都会生成一个Chunk Header,对应图中的「@@」和「@@」符号之间。

@@ -31,21 +31,25 @@

这里表示,从A版本的第31行开始,变更了21行,B版本从31行开始,变更了25行。

但是,我只是加了4行log啊,这是什么鬼??

其实git diff指令不仅仅会给出变更行,而且还会带上前后默认3行的修改信息,作为上下文,所以才会有这么多的修改。

所以,我们需要再利用git的一个指令:

--unified=<n>,简写为-U<n>

来指定上下文关联的代码行数,这里设置为-U0,表示只关心实际的变更。

加上这个参数后,输出如下:

从精准化测试看ASM在Android中的强势插入-读懂diffimage-20210625145214250

加了这个参数后,Chunk Header同样会有三种情况:

  • -/+号后面只有一个数字,设为N,那么表示增加(+)、删除(-)了1行,行号为N,例如+34,就是第34行,增加了1行。

  • -/+号后面有两个数字,第1个数字设为N,且第二个数字为0,那么表示第N行没有变化,增加(+)、删除(-)了0行,这有啥意义呢?其实这就表示该内容是新增的。

  • -/+号后面有两个数字,第1个数字设为N,第二个数字为M,那么表示从N行开始,增加(+)、删除(-)了M行,这用于标记多行的修改。

那么有了这样一个认知后,就可以通过正则来检出这些数据。

git diff HEAD~1 HEAD -U0 | ggrep -Po '^\+\+\+ ./\K.*|^@@ -[0-9]+(,[0-9]+)? \+\K[0-9]+(,[0-9]+)?(?= @@)'

借助这样一个正则表达式和grep,就可以从diff信息中找出修改的文件和行号,执行如下:

app/src/main/java/com/yw/qdcoverage/MainActivity.kt
34
40
46
52

这里要注意,Mac下grep是用的2.5.1-FreeBSD版本,所以不支持 -P指令,需要安装GNU Grep,通过brew install grep来安装即可,安装好之后,通过ggrep来调用GNU Grep(如果是Linux的话,那么可以直接使用grep)。

如果在脚本中,可以借助正则表达式来获取。

Pattern.compile("^@@ -(\\d+),?(\\d+)? \\+(\\d+),?(\\d+)? @@.*");

这样通过下面的代码就可以获取新文件的修改行:

matcher.group(3)
matcher.group(4)

以上就是我们获取增量信息的基础,借助git的这些指令,我们就为后续JaCoco探针的插入,提供了Diff的信息,从而可以实现增量探针机制。

 

作者:徐宜生

 

上一篇:Java ASM系列:(035)TraceClassVisitor介绍


下一篇:Java ASM系列一:Core API