筆者最近在做單元測試框架的搭建璧南,做到輸出代碼覆蓋率的時候掌逛,發(fā)現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)
但是,Test target還是有報告出來的妒穴。
為此宋税,筆者幾乎搜遍了google baidu bing,沒有一個相同的問題讼油,我想我遇到了所有人都沒有遇到過的棘手的問題弃甥。
·初步思考
1、工程比較復雜汁讼,可能沒法正常輸出結果
2、XCode可能有bug阔墩,就是沒法輸出結果
3嘿架、工程有問題,沒法正常輸出結果
·并嘗試解決:
1啸箫、通過新建一個空的工程耸彪,并跑一次單元測試,正常輸出忘苛。
2蝉娜、新工程引入一個原工程的文件,并把各種庫鏈接完成扎唾,跑一次單元測試召川,并重復幾次操作,發(fā)現胸遇,當庫文件還比較少時荧呐,還是可以輸出測試報告,但一旦庫引用接近原工程時纸镊,報告就無法生成了倍阐。
最后,初步定性為3逗威,工程有問題峰搪,結合網上壓根沒有相關的資料,就比較肯定了凯旭,畢竟筆者的工程結構是真的過于復雜概耻。
·嘗試細致定位問題:
工程有問題使套,可能是其中某個庫導致的。著手嘗試逐個庫引入并進行單元測試咐蚯,最終發(fā)現引入libprotobuf時童漩,同時other linker flag 設為-ObjC時,會出現該問題春锋。-ObjC標記是鏈接時矫膨,會將所有靜態(tài)庫中的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怯屉,比較完整的方案,而且精測基本上走得這個方案饵沧,可行性高锨络,社區(qū)信息豐富。
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工具執(zhí)行 show的時候褥傍,都會報同樣的錯誤 Failed to decompress coverage data (zlib)。
只能迎難而上喇聊,找出問題的根本原因了。也就是工程為什么會導致該搞錯蹦狂。
1誓篱、下載LLVM Project:
2、執(zhí)行 All_Build target凯楔,完整編譯一次llvm
3窜骄、找到llvm-cov工具文件,閱讀源碼摆屯,并進行Debug
4邻遏、使用xcode生成的profdata,執(zhí)行l(wèi)lvm-cov show命令虐骑,并進行斷點研究
有幾個發(fā)現准验,
1、zlib其實是對二進制碼進行解壓的工具廷没,llvm執(zhí)行編譯時糊饱,是通過對代碼插樁,從而收集代碼執(zhí)行的情況颠黎。.profdata應該是執(zhí)行的Log另锋。llvm-cov show是通過zlib解壓二進制包和代碼滞项,關聯.profdata,最終整合出報告夭坪。
2文判、llvm-cov show時崩潰,觸發(fā)了CoverageMappingReader.cpp中的assert((CovMapVersion)CovHeader->getVersion<Endian>() == Version)室梅。原因是getVersion = 2, Version = 4 戏仓。
3、注釋掉該行assert代碼竞惋,程序跑完后報錯 Failed to decompress coverage data (zlib)柜去。
問題關鍵點找到了!
但是拆宛,為什么呢嗓奢?
繼續(xù)對llvm-cov show執(zhí)行過程進行debug,發(fā)現浑厚,其實zlib解析了大部分代碼都是正常的股耽,但是一到某個地方就version對不上,上下文也無法定位钳幅,因為解析的信息出來是空的物蝙。
經過對代碼繼續(xù)排查,試圖找到哪個包出現問題敢艰,最后發(fā)現诬乞,還是執(zhí)行到解析libprotobuf相關代碼出現問題。
醉了钠导,最終還是libprotobuf的問題震嫉,反思了一下,之前force_load時為什么還是不行牡属?而且票堵,libprotobuf的代碼不應該在里面,因為其他靜態(tài)庫文件都是沒有在里面的逮栅,libprotobuf應該沒有插樁的啊悴势,怎么會收集上來呢。
重新拉protobuf工程措伐,編一個iOS的lib特纤,替換,執(zhí)行單測侥加。
結果叫潦,報告出來了,醉了醉了〈H铮肯定是原來的包混進了奇怪的東西短蜕。(后面對比了一下,好像也沒什么問題傻咖,確實protobuf的.m會被插樁朋魔,protobuf-iOS的編譯跟普通的庫還是不一樣,難道是版本編譯出的問題卿操?對于llvm還是不熟警检,最終原因還有待確定)
總結:
本次問題還是很棘手的,每一步都沒有參考的方法和思路害淤。由于xcode是黑盒扇雕,不知道用的哪種代碼覆蓋收集方案導致一開始就沒有走正確的路線。其次窥摄,一提到llvm覺得是很難的東西镶奉,沒有靜下心來細看。最后崭放,還是因為知識匱乏哨苛,導致彎路走了很多。經此一戰(zhàn)币砂,以后llvm工具報錯出問題都不用擔心了建峭。