笔者最近在做单元测试框架的搭建,做到输出代码覆盖率的时候,发现app的代码覆盖率一直是0,然后就开启了历程一周的找碴过程,其中嫌弃电脑太慢,直接花了1w4买入了新的macbook pro(M1 pro),不得不说,生产力杠杠的,不过现在系统有个问题会造成播放音乐时出现噗噗的声音有点烦人。
好,进入主题:
问题描述:
如图,跑完单元测试后XCode Coverage Report 显示为零。Log显示提示语是:
Failed to generate coverage for target 'XXX.app' at paths (xxxxxxx): Failed to decompress coverage data (zlib)
image.png
但是,Test target还是有报告出来的。
为此,笔者几乎搜遍了google baidu bing,没有一个相同的问题,我想我遇到了所有人都没有遇到过的棘手的问题。
·初步思考
1、工程比较复杂,可能没法正常输出结果
2、XCode可能有bug,就是没法输出结果
3、工程有问题,没法正常输出结果
·并尝试解决:
1、通过新建一个空的工程,并跑一次单元测试,正常输出。
2、新工程引入一个原工程的文件,并把各种库链接完成,跑一次单元测试,并重复几次操作,发现,当库文件还比较少时,还是可以输出测试报告,但一旦库引用接近原工程时,报告就无法生成了。
最后,初步定性为3,工程有问题,结合网上压根没有相关的资料,就比较肯定了,毕竟笔者的工程结构是真的过于复杂。
·尝试细致定位问题:
工程有问题,可能是其中某个库导致的。着手尝试逐个库引入并进行单元测试,最终发现引入libprotobuf时,同时other linker flag 设为-ObjC时,会出现该问题。-ObjC标记是链接时,会将所有静态库中的Objective C class和category加载到包中。那估计就是libprotobuf中的东西引进去导致了问题。
试图绕开libprotobuf,通过force_load逐个将需要的包加载,结果,失败。
来到这里,人都崩溃了,搞了大半天,回到原点,可能并不是单单libprotobuf的问题,有可能是加载Objective C category的问题。
·高阶尝试:
绕开不行,那我换一种单元测试方案,不用XCode输出报告了,直接走精准测试的方案,毕竟XCode是个黑盒,查了那么多资料至今也不知道XCode的覆盖率报告用的是哪个方案(后来知道了)。
1、llvm gcov方案 ,通过.gcda .gcno的方案,但是无法检测swift的代码
2、llvm Source-Based Code Coverage方案,通过生成.profraw .profdata,比较完整的方案,而且精测基本上走得这个方案,可行性高,社区信息丰富。
3、SanitizerCoverage,另一种插桩方式,几乎没人用来做覆盖率报告,难度高
经过实施,
第1方案是可行的,但是无法生成swift的代码
第2方案,历经千辛万苦,最后工具报错 Failed to decompress coverage data (zlib)。
崩溃了。。。
可以看出,其实XCode的单元测试代码覆盖率报告是用llvm Source-Based Code Coverage的方案进行的,只是最后在.profdata输出的基础上进行ui层面的加工,我们也可以在编译中间文件中找到Coverage.profdata。
第3方案,是一个大工程,根本不可能短时间完成,但是几乎可以肯定,报告肯定可以做出来的。
·lvm-cov Source-Based Code Coverage方案Deep Dive:
无论通过自己走llvm SourceBaseCodeCoverage方案生成的profdata,还是xcode收集的profdata,在llvm-cov工具执行 show的时候,都会报同样的错误 Failed to decompress coverage data (zlib)。
只能迎难而上,找出问题的根本原因了。也就是工程为什么会导致该搞错。
1、下载LLVM Project:
2、执行 All_Build target,完整编译一次llvm
3、找到llvm-cov工具文件,阅读源码,并进行Debug
4、使用xcode生成的profdata,执行llvm-cov show命令,并进行断点研究
有几个发现,
1、zlib其实是对二进制码进行解压的工具,llvm执行编译时,是通过对代码插桩,从而收集代码执行的情况。.profdata应该是执行的Log。llvm-cov show是通过zlib解压二进制包和代码,关联.profdata,最终整合出报告。
2、llvm-cov show时崩溃,触发了CoverageMappingReader.cpp中的assert((CovMapVersion)CovHeader->getVersion<Endian>() == Version)。原因是getVersion = 2, Version = 4 。
3、注释掉该行assert代码,程序跑完后报错 Failed to decompress coverage data (zlib)。
问题关键点找到了!
但是,为什么呢?
继续对llvm-cov show执行过程进行debug,发现,其实zlib解析了大部分代码都是正常的,但是一到某个地方就version对不上,上下文也无法定位,因为解析的信息出来是空的。
经过对代码继续排查,试图找到哪个包出现问题,最后发现,还是执行到解析libprotobuf相关代码出现问题。
醉了,最终还是libprotobuf的问题,反思了一下,之前force_load时为什么还是不行?而且,libprotobuf的代码不应该在里面,因为其他静态库文件都是没有在里面的,libprotobuf应该没有插桩的啊,怎么会收集上来呢。
重新拉protobuf工程,编一个iOS的lib,替换,执行单测。
结果,报告出来了,醉了醉了。肯定是原来的包混进了奇怪的东西。(后面对比了一下,好像也没什么问题,确实protobuf的.m会被插桩,protobuf-iOS的编译跟普通的库还是不一样,难道是版本编译出的问题?对于llvm还是不熟,最终原因还有待确定)
总结:
本次问题还是很棘手的,每一步都没有参考的方法和思路。由于xcode是黑盒,不知道用的哪种代码覆盖收集方案导致一开始就没有走正确的路线。其次,一提到llvm觉得是很难的东西,没有静下心来细看。最后,还是因为知识匮乏,导致弯路走了很多。经此一战,以后llvm工具报错出问题都不用担心了。
网友评论